From: Julien HENRY Date: Thu, 25 Sep 2014 11:48:35 +0000 (+0200) Subject: SONAR-5644 Rework SCM API X-Git-Tag: 5.0-RC1~921 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a4e76de07797ea2b9b809a0cc0b90286c90cd3cd;p=sonarqube.git SONAR-5644 Rework SCM API --- diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java new file mode 100644 index 00000000000..0d8b447cdd8 --- /dev/null +++ b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java @@ -0,0 +1,96 @@ +/* + * 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.git; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +public class GitBlameCommand implements BlameCommand { + + private static final Logger LOG = LoggerFactory.getLogger(GitBlameCommand.class); + private final CommandExecutor commandExecutor; + + public GitBlameCommand() { + this(CommandExecutor.create()); + } + + GitBlameCommand(CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + } + + @Override + public void blame(FileSystem fs, Iterable files, BlameResult result) { + for (InputFile inputFile : files) { + String filename = inputFile.relativePath(); + Command cl = createCommandLine(fs.baseDir(), filename); + GitBlameConsumer consumer = new GitBlameConsumer(LOG); + StringStreamConsumer stderr = new StringStreamConsumer(); + + int exitCode = execute(cl, consumer, stderr); + if (exitCode != 0) { + throw new IllegalStateException("The git blame command [" + cl.toString() + "] failed: " + stderr.getOutput()); + } + result.add(inputFile, consumer.getLines()); + } + } + + public int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) { + LOG.info("Executing: " + cl); + LOG.info("Working directory: " + cl.getDirectory().getAbsolutePath()); + + return commandExecutor.execute(cl, consumer, stderr, 10 * 1000); + } + + private Command createCommandLine(File workingDirectory, String filename) { + Command cl = Command.create("git"); + cl.addArgument("blame"); + if (workingDirectory != null) { + cl.setDirectory(workingDirectory); + } + cl.addArgument("--porcelain"); + cl.addArgument(filename); + cl.addArgument("-w"); + 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-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java new file mode 100644 index 00000000000..1c7ef86cb98 --- /dev/null +++ b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java @@ -0,0 +1,189 @@ +/* + * 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.git; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.sonar.api.batch.scm.BlameLine; +import org.sonar.api.utils.command.StreamConsumer; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Plain copy of package org.apache.maven.scm.provider.git.gitexe.command.blame.GitBlameConsumer + * Patched to allow user email retrieval when parsing Git blame results. + * + * @Todo: hack - to be submitted as an update in maven-scm-api for a future release + * + *

+ * For more information, see: + * DEVACT-103 + * + * @since 1.5.1 + */ +public class GitBlameConsumer implements StreamConsumer { + + private static final String GIT_COMMITTER_PREFIX = "committer"; + private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time "; + private static final String GIT_AUTHOR_EMAIL = "author-mail "; + private static final String GIT_COMMITTER_EMAIL = GIT_COMMITTER_PREFIX + "-mail "; + private static final String OPENING_EMAIL_FIELD = "<"; + private static final String CLOSING_EMAIL_FIELD = ">"; + + private List lines = new ArrayList(); + + /** + * Since the porcelain format only contains the commit information + * the first time a specific sha-1 commit appears, we need to store + * this information somwehere. + *

+ * key: the sha-1 of the commit + * value: the {@link BlameLine} containing the full committer/author info + */ + private Map commitInfo = new HashMap(); + + private boolean expectRevisionLine = true; + + private String revision = null; + private String author = null; + private String committer = null; + private Date time = null; + private Logger logger; + + public Logger getLogger() { + return logger; + } + + public GitBlameConsumer(Logger logger) { + this.logger = logger; + } + + public void consumeLine(String line) { + if (line == null) { + return; + } + + if (expectRevisionLine) { + // this is the revision line + consumeRevisionLine(line); + } else { + + if (extractCommitInfoFromLine(line)) { + return; + } + + if (line.startsWith("\t")) { + // this is the content line. + // we actually don't need the content, but this is the right time to add the blame line + consumeContentLine(); + } + } + } + + @VisibleForTesting + protected boolean extractCommitInfoFromLine(String line) { + if (line.startsWith(GIT_AUTHOR_EMAIL)) { + author = extractEmail(line); + return true; + } + + if (line.startsWith(GIT_COMMITTER_EMAIL)) { + committer = extractEmail(line); + return true; + } + + if (line.startsWith(GIT_COMMITTER_TIME)) { + String timeStr = line.substring(GIT_COMMITTER_TIME.length()); + time = new Date(Long.parseLong(timeStr) * 1000L); + return true; + } + return false; + } + + @VisibleForTesting + protected String getAuthor() { + return author; + } + + @VisibleForTesting + protected String getCommitter() { + return committer; + } + + @VisibleForTesting + protected Date getTime() { + return time; + } + + private String extractEmail(String line) { + + int emailStartIndex = line.indexOf(OPENING_EMAIL_FIELD); + int emailEndIndex = line.indexOf(CLOSING_EMAIL_FIELD); + + if (emailStartIndex == -1 || emailEndIndex == -1 || emailEndIndex <= emailStartIndex) { + return null; + } + return line.substring(emailStartIndex + 1, emailEndIndex); + } + + private void consumeContentLine() { + BlameLine blameLine = new BlameLine(time, revision, author, committer); + getLines().add(blameLine); + + // keep commitinfo for this sha-1 + commitInfo.put(revision, blameLine); + + if (getLogger().isDebugEnabled()) { + DateFormat df = SimpleDateFormat.getDateTimeInstance(); + getLogger().debug(author + " " + df.format(time)); + } + + expectRevisionLine = true; + } + + private void consumeRevisionLine(String line) { + String[] parts = line.split("\\s", 4); + + if (parts.length >= 1) { + revision = parts[0]; + + BlameLine oldLine = commitInfo.get(revision); + + if (oldLine != null) { + // restore the commit info + author = oldLine.getAuthor(); + committer = oldLine.getCommitter(); + time = oldLine.getDate(); + } + + expectRevisionLine = false; + } + } + + public List getLines() { + return lines; + } +} diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitPlugin.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitPlugin.java index 033cef7b7de..e5c12c42900 100644 --- a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitPlugin.java +++ b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitPlugin.java @@ -28,7 +28,8 @@ public final class GitPlugin extends SonarPlugin { public List getExtensions() { return ImmutableList.of( - GitScmProvider.class); + GitScmProvider.class, + GitBlameCommand.class); } } diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java index 7fbfe516c20..a0dfeb92601 100644 --- a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java +++ b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java @@ -19,20 +19,18 @@ */ package org.sonar.plugins.scm.git; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -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.batch.scm.ScmProvider; -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; -public class GitScmProvider implements ScmProvider { +public class GitScmProvider extends ScmProvider { - private static final Logger LOG = LoggerFactory.getLogger(GitScmProvider.class); + private GitBlameCommand blameCommand; + + public GitScmProvider(GitBlameCommand blameCommand) { + this.blameCommand = blameCommand; + } @Override public String key() { @@ -45,53 +43,8 @@ public class GitScmProvider implements ScmProvider { } @Override - public void blame(FileSystem fs, Iterable files, BlameResult result) { - for (InputFile inputFile : files) { - String filename = inputFile.relativePath(); - Command cl = createCommandLine(fs.baseDir(), filename); - SonarGitBlameConsumer consumer = new SonarGitBlameConsumer(LOG); - StringStreamConsumer stderr = new StringStreamConsumer(); - - int exitCode = execute(cl, consumer, stderr); - if (exitCode != 0) { - throw new IllegalStateException("The git blame command [" + cl.toString() + "] failed: " + stderr.getOutput()); - } - result.add(inputFile, consumer.getLines()); - } - } - - public static int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) { - LOG.info("Executing: " + cl); - LOG.info("Working directory: " + cl.getDirectory().getAbsolutePath()); - - return CommandExecutor.create().execute(cl, consumer, stderr, 10 * 1000); - } - - private static Command createCommandLine(File workingDirectory, String filename) { - Command cl = Command.create("git"); - cl.addArgument("blame"); - if (workingDirectory != null) { - cl.setDirectory(workingDirectory); - } - cl.addArgument("--porcelain"); - cl.addArgument(filename); - cl.addArgument("-w"); - 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(); - } + public BlameCommand blameCommand() { + return this.blameCommand; } } diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java deleted file mode 100644 index 3102b00f105..00000000000 --- a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Sonar SCM Activity Plugin - * Copyright (C) 2010 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.plugins.scm.git; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.sonar.api.batch.scm.BlameLine; -import org.sonar.api.utils.command.StreamConsumer; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Plain copy of package org.apache.maven.scm.provider.git.gitexe.command.blame.GitBlameConsumer - * Patched to allow user email retrieval when parsing Git blame results. - * - * @Todo: hack - to be submitted as an update in maven-scm-api for a future release - * - *

- * For more information, see: - * DEVACT-103 - * - * @since 1.5.1 - */ -public class SonarGitBlameConsumer implements StreamConsumer { - - private static final String GIT_COMMITTER_PREFIX = "committer"; - private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time "; - private static final String GIT_AUTHOR_EMAIL = "author-mail "; - private static final String GIT_COMMITTER_EMAIL = GIT_COMMITTER_PREFIX + "-mail "; - private static final String OPENING_EMAIL_FIELD = "<"; - private static final String CLOSING_EMAIL_FIELD = ">"; - - private List lines = new ArrayList(); - - /** - * Since the porcelain format only contains the commit information - * the first time a specific sha-1 commit appears, we need to store - * this information somwehere. - *

- * key: the sha-1 of the commit - * value: the {@link BlameLine} containing the full committer/author info - */ - private Map commitInfo = new HashMap(); - - private boolean expectRevisionLine = true; - - private String revision = null; - private String author = null; - private String committer = null; - private Date time = null; - private Logger logger; - - public Logger getLogger() { - return logger; - } - - public SonarGitBlameConsumer(Logger logger) { - this.logger = logger; - } - - public void consumeLine(String line) { - if (line == null) { - return; - } - - if (expectRevisionLine) { - // this is the revision line - consumeRevisionLine(line); - } else { - - if (extractCommitInfoFromLine(line)) { - return; - } - - if (line.startsWith("\t")) { - // this is the content line. - // we actually don't need the content, but this is the right time to add the blame line - consumeContentLine(); - } - } - } - - @VisibleForTesting - protected boolean extractCommitInfoFromLine(String line) { - if (line.startsWith(GIT_AUTHOR_EMAIL)) { - author = extractEmail(line); - return true; - } - - if (line.startsWith(GIT_COMMITTER_EMAIL)) { - committer = extractEmail(line); - return true; - } - - if (line.startsWith(GIT_COMMITTER_TIME)) { - String timeStr = line.substring(GIT_COMMITTER_TIME.length()); - time = new Date(Long.parseLong(timeStr) * 1000L); - return true; - } - return false; - } - - @VisibleForTesting - protected String getAuthor() { - return author; - } - - @VisibleForTesting - protected String getCommitter() { - return committer; - } - - @VisibleForTesting - protected Date getTime() { - return time; - } - - private String extractEmail(String line) { - - int emailStartIndex = line.indexOf(OPENING_EMAIL_FIELD); - int emailEndIndex = line.indexOf(CLOSING_EMAIL_FIELD); - - if (emailStartIndex == -1 || emailEndIndex == -1 || emailEndIndex <= emailStartIndex) { - return null; - } - return line.substring(emailStartIndex + 1, emailEndIndex); - } - - private void consumeContentLine() { - BlameLine blameLine = new BlameLine(time, revision, author, committer); - getLines().add(blameLine); - - // keep commitinfo for this sha-1 - commitInfo.put(revision, blameLine); - - if (getLogger().isDebugEnabled()) { - DateFormat df = SimpleDateFormat.getDateTimeInstance(); - getLogger().debug(author + " " + df.format(time)); - } - - expectRevisionLine = true; - } - - private void consumeRevisionLine(String line) { - String[] parts = line.split("\\s", 4); - - if (parts.length >= 1) { - revision = parts[0]; - - BlameLine oldLine = commitInfo.get(revision); - - if (oldLine != null) { - // restore the commit info - author = oldLine.getAuthor(); - committer = oldLine.getCommitter(); - time = oldLine.getDate(); - } - - expectRevisionLine = false; - } - } - - public List getLines() { - return lines; - } -} diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java new file mode 100644 index 00000000000..805b0222af3 --- /dev/null +++ b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java @@ -0,0 +1,108 @@ +/* + * 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.git; + +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 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 GitBlameCommandTest { + + @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() { + + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + StreamConsumer outConsumer = (StreamConsumer) invocation.getArguments()[1]; + outConsumer.consumeLine("2c68c473da7fc293e12ca50f19380c5118be7ead 68 54 1"); + outConsumer.consumeLine("author Simon Brandhof"); + outConsumer.consumeLine("author-mail "); + outConsumer.consumeLine("author-time 1312534171"); + outConsumer.consumeLine("author-tz +0200"); + outConsumer.consumeLine("committer Simon Brandhof"); + outConsumer.consumeLine("committer-mail "); + outConsumer.consumeLine("committer-time 1312534171"); + outConsumer.consumeLine("committer-tz +0200"); + outConsumer.consumeLine("summary Move to nexus.codehaus.org + configuration of maven release plugin is back"); + outConsumer.consumeLine("previous 1bec1c3a77f6957175be13e4433110f7fc8e387e pom.xml"); + outConsumer.consumeLine("filename pom.xml"); + outConsumer.consumeLine("\tcodehaus-nexus-staging"); + outConsumer.consumeLine("2c68c473da7fc293e12ca50f19380c5118be7ead 72 60 1"); + outConsumer.consumeLine("\t${sonar.snapshotRepository.url}"); + return 0; + } + }); + + new GitBlameCommand(commandExecutor).blame(fs, Arrays.asList(inputFile), result); + verify(result).add(inputFile, + Arrays.asList(new BlameLine(DateUtils.parseDateTime("2011-08-05T10:49:31+0200"), "2c68c473da7fc293e12ca50f19380c5118be7ead", "simon.brandhof@gmail.com"), + new BlameLine(DateUtils.parseDateTime("2011-08-05T10:49:31+0200"), "2c68c473da7fc293e12ca50f19380c5118be7ead", "simon.brandhof@gmail.com"))); + } + +} diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitPluginTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitPluginTest.java index d65e0eb5844..d175cdcbe91 100644 --- a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitPluginTest.java +++ b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitPluginTest.java @@ -27,6 +27,6 @@ public class GitPluginTest { @Test public void getExtensions() { - assertThat(new GitPlugin().getExtensions()).hasSize(1); + assertThat(new GitPlugin().getExtensions()).hasSize(2); } } diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java new file mode 100644 index 00000000000..9bc5801d055 --- /dev/null +++ b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java @@ -0,0 +1,53 @@ +/* + * 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.git; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; + +public class GitScmProviderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void sanityCheck() { + assertThat(new GitScmProvider(null).key()).isEqualTo("git"); + GitBlameCommand blameCommand = new GitBlameCommand(); + assertThat(new GitScmProvider(blameCommand).blameCommand()).isEqualTo(blameCommand); + } + + @Test + public void testAutodetection() throws IOException { + File baseDirEmpty = temp.newFolder(); + assertThat(new GitScmProvider(null).supports(baseDirEmpty)).isFalse(); + + File gitBaseDir = temp.newFolder(); + new File(gitBaseDir, ".git").mkdir(); + assertThat(new GitScmProvider(null).supports(gitBaseDir)).isTrue(); + } + +} diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java deleted file mode 100644 index a1f34593f1b..00000000000 --- a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.git.medium; - -import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.sensor.duplication.DuplicationGroup; -import org.sonar.batch.mediumtest.BatchMediumTester; -import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.plugins.scm.git.GitPlugin; -import org.sonar.xoo.XooPlugin; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static org.fest.assertions.Assertions.assertThat; - -public class GitMediumTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - public BatchMediumTester tester = BatchMediumTester.builder() - .registerPlugin("xoo", new XooPlugin()) - .registerPlugin("git", new GitPlugin()) - .addDefaultQProfile("xoo", "Sonar Way") - .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) - .build(); - - private File baseDir; - - private ImmutableMap.Builder builder; - - @Before - public void prepare() throws IOException { - tester.start(); - - baseDir = temp.newFolder(); - - builder = ImmutableMap.builder() - .put("sonar.task", "scan") - .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) - .put("sonar.projectKey", "com.foo.project") - .put("sonar.projectName", "Foo Project") - .put("sonar.projectVersion", "1.0-SNAPSHOT") - .put("sonar.projectDescription", "Description of Foo Project"); - } - - @After - public void stop() { - tester.stop(); - } - - @Test - public void testDuplications() throws IOException { - File srcDir = new File(baseDir, "src"); - srcDir.mkdir(); - - String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti"; - - File xooFile1 = new File(srcDir, "sample1.xoo"); - FileUtils.write(xooFile1, duplicatedStuff); - - File xooFile2 = new File(srcDir, "sample2.xoo"); - FileUtils.write(xooFile2, duplicatedStuff); - - TaskResult result = tester.newTask() - .properties(builder - .put("sonar.sources", "src") - .put("sonar.cpd.xoo.minimumTokens", "10") - .put("sonar.verbose", "true") - .build()) - .start(); - - assertThat(result.inputFiles()).hasSize(2); - - // 4 measures per file - assertThat(result.measures()).hasSize(8); - - InputFile inputFile = result.inputFiles().get(0); - // One clone group - List duplicationGroups = result.duplicationsFor(inputFile); - assertThat(duplicationGroups).hasSize(1); - - DuplicationGroup cloneGroup = duplicationGroups.get(0); - assertThat(cloneGroup.duplicates()).hasSize(1); - assertThat(cloneGroup.originBlock().startLine()).isEqualTo(1); - assertThat(cloneGroup.originBlock().length()).isEqualTo(17); - } - -} diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java index e1988ac37ab..148a6df0b7c 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java @@ -25,13 +25,14 @@ import org.sonar.xoo.lang.MeasureSensor; import org.sonar.xoo.lang.SymbolReferencesSensor; import org.sonar.xoo.lang.SyntaxHighlightingSensor; import org.sonar.xoo.lang.TestCaseSensor; -import org.sonar.xoo.lang.XooScmProvider; import org.sonar.xoo.lang.XooTokenizerSensor; import org.sonar.xoo.rule.CreateIssueByInternalKeySensor; import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; import org.sonar.xoo.rule.OneIssuePerLineSensor; import org.sonar.xoo.rule.XooQualityProfile; import org.sonar.xoo.rule.XooRulesDefinition; +import org.sonar.xoo.scm.XooBlameCommand; +import org.sonar.xoo.scm.XooScmProvider; import java.util.Arrays; import java.util.List; @@ -53,6 +54,7 @@ public class XooPlugin extends SonarPlugin { // SCM XooScmProvider.class, + XooBlameCommand.class, // sensors MeasureSensor.class, diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java deleted file mode 100644 index 895d47dfbbe..00000000000 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.xoo.lang; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.scm.BlameLine; -import org.sonar.api.batch.scm.ScmProvider; -import org.sonar.api.config.Settings; -import org.sonar.api.utils.DateUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class XooScmProvider implements ScmProvider { - - private static final Logger LOG = LoggerFactory.getLogger(XooScmProvider.class); - - private static final String SCM_EXTENSION = ".scm"; - - private final Settings settings; - - public XooScmProvider(Settings settings) { - this.settings = settings; - } - - @Override - public String key() { - return "xoo"; - } - - @Override - public boolean supports(File baseDir) { - return false; - } - - @Override - public void blame(Iterable files, BlameResult handler) { - for (InputFile inputFile : files) { - processFile(inputFile, handler); - } - } - - @VisibleForTesting - protected void processFile(InputFile inputFile, BlameResult handler) { - File ioFile = inputFile.file(); - File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION); - if (!scmDataFile.exists()) { - throw new IllegalStateException("Missing file " + scmDataFile); - } - - try { - List lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name()); - List blame = new ArrayList(lines.size()); - int lineNumber = 0; - for (String line : lines) { - lineNumber++; - if (StringUtils.isNotBlank(line)) { - // revision,author,dateTime - String[] fields = StringUtils.split(line, ','); - if (fields.length < 3) { - throw new IllegalStateException("Not enough fields on line " + lineNumber); - } - String revision = fields[0]; - String author = fields[1]; - // Will throw an exception, when date is not in format "yyyy-MM-dd" - Date date = DateUtils.parseDate(fields[2]); - - blame.add(new BlameLine(date, revision, author)); - } - } - handler.add(inputFile, blame); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java new file mode 100644 index 00000000000..9963cc34bd9 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java @@ -0,0 +1,23 @@ +/* + * 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.xoo.lang; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java index 7a11c639e38..31db08c01f2 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java @@ -32,7 +32,7 @@ public class OneIssuePerLineSensor implements Sensor { public static final String RULE_KEY = "OneIssuePerLine"; private static final String EFFORT_TO_FIX_PROPERTY = "sonar.oneIssuePerLine.effortToFix"; - private static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity"; + public static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity"; @Override public void describe(SensorDescriptor descriptor) { diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java new file mode 100644 index 00000000000..7d492cc315b --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java @@ -0,0 +1,82 @@ +/* + * 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.xoo.scm; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +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.batch.scm.BlameLine; +import org.sonar.api.utils.DateUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class XooBlameCommand implements BlameCommand { + + private static final String SCM_EXTENSION = ".scm"; + + @Override + public void blame(FileSystem fs, Iterable files, BlameResult result) { + for (InputFile inputFile : files) { + processFile(inputFile, result); + } + } + + @VisibleForTesting + protected void processFile(InputFile inputFile, BlameResult result) { + File ioFile = inputFile.file(); + File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION); + if (!scmDataFile.exists()) { + throw new IllegalStateException("Missing file " + scmDataFile); + } + + try { + List lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name()); + List blame = new ArrayList(lines.size()); + int lineNumber = 0; + for (String line : lines) { + lineNumber++; + if (StringUtils.isNotBlank(line)) { + // revision,author,dateTime + String[] fields = StringUtils.split(line, ','); + if (fields.length < 3) { + throw new IllegalStateException("Not enough fields on line " + lineNumber); + } + String revision = fields[0]; + String author = fields[1]; + // Will throw an exception, when date is not in format "yyyy-MM-dd" + Date date = DateUtils.parseDate(fields[2]); + + blame.add(new BlameLine(date, revision, author)); + } + } + result.add(inputFile, blame); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java new file mode 100644 index 00000000000..5807d820043 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java @@ -0,0 +1,43 @@ +/* + * 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.xoo.scm; + +import org.sonar.api.batch.scm.BlameCommand; +import org.sonar.api.batch.scm.ScmProvider; + +public class XooScmProvider extends ScmProvider { + + private final XooBlameCommand blame; + + public XooScmProvider(XooBlameCommand blame) { + this.blame = blame; + } + + @Override + public String key() { + return "xoo"; + } + + @Override + public BlameCommand blameCommand() { + return blame; + } + +} diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java new file mode 100644 index 00000000000..f5c68e3c563 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java @@ -0,0 +1,23 @@ +/* + * 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.xoo.scm; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java index 96ea0ea923c..e8100740345 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java @@ -27,6 +27,6 @@ public class XooPluginTest { @Test public void provide_extensions() { - assertThat(new XooPlugin().getExtensions()).hasSize(13); + assertThat(new XooPlugin().getExtensions()).hasSize(14); } } diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java new file mode 100644 index 00000000000..31d109e3653 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java @@ -0,0 +1,104 @@ +/* + * 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.xoo.rule; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorStorage; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.Issue.Severity; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.config.Settings; +import org.sonar.xoo.Xoo; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class OneIssuePerLineSensorTest { + + private OneIssuePerLineSensor sensor = new OneIssuePerLineSensor(); + + @Test + public void testDescriptor() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); + sensor.describe(descriptor); + assertThat(descriptor.ruleRepositories()).containsOnly(XooRulesDefinition.XOO_REPOSITORY); + } + + @Test + public void testRule() { + DefaultFileSystem fs = new DefaultFileSystem(); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.xoo").setLanguage(Xoo.KEY).setLines(10); + fs.add(inputFile); + + SensorContext context = mock(SensorContext.class); + final SensorStorage sensorStorage = mock(SensorStorage.class); + when(context.settings()).thenReturn(new Settings()); + when(context.fileSystem()).thenReturn(fs); + when(context.newIssue()).thenAnswer(new Answer() { + @Override + public Issue answer(InvocationOnMock invocation) throws Throwable { + return new DefaultIssue(sensorStorage); + } + }); + sensor.execute(context); + + ArgumentCaptor argCaptor = ArgumentCaptor.forClass(DefaultIssue.class); + verify(sensorStorage, times(10)).store(argCaptor.capture()); + assertThat(argCaptor.getAllValues()).hasSize(10); // One issue per line + assertThat(argCaptor.getValue().overridenSeverity()).isNull(); + } + + @Test + public void testForceSeverity() { + DefaultFileSystem fs = new DefaultFileSystem(); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.xoo").setLanguage(Xoo.KEY).setLines(10); + fs.add(inputFile); + + SensorContext context = mock(SensorContext.class); + final SensorStorage sensorStorage = mock(SensorStorage.class); + Settings settings = new Settings(); + settings.setProperty(OneIssuePerLineSensor.FORCE_SEVERITY_PROPERTY, "MINOR"); + when(context.settings()).thenReturn(settings); + when(context.fileSystem()).thenReturn(fs); + when(context.newIssue()).thenAnswer(new Answer() { + @Override + public Issue answer(InvocationOnMock invocation) throws Throwable { + return new DefaultIssue(sensorStorage); + } + }); + sensor.execute(context); + + ArgumentCaptor argCaptor = ArgumentCaptor.forClass(DefaultIssue.class); + verify(sensorStorage, times(10)).store(argCaptor.capture()); + assertThat(argCaptor.getAllValues()).hasSize(10); // One issue per line + assertThat(argCaptor.getValue().overridenSeverity()).isEqualTo(Severity.MINOR); + } + +} diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java new file mode 100644 index 00000000000..bba495dd400 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java @@ -0,0 +1,75 @@ +/* + * 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.xoo.scm; + +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.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.xoo.Xoo; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class XooBlameCommandTest { + + @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(); + } + + @Test + public void testBlame() throws IOException { + File source = new File(baseDir, "src/foo.xoo"); + FileUtils.write(source, "sample content"); + File scm = new File(baseDir, "src/foo.xoo.scm"); + FileUtils.write(scm, "123,julien,2014-12-12\n234,julien,2014-12-24"); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setAbsolutePath(new File(baseDir, "src/foo.xoo").getAbsolutePath()).setLanguage(Xoo.KEY); + fs.add(inputFile); + + BlameResult result = mock(BlameResult.class); + new XooBlameCommand().blame(fs, Arrays.asList(inputFile), result); + verify(result).add(inputFile, Arrays.asList(new BlameLine(DateUtils.parseDate("2014-12-12"), "123", "julien"), + new BlameLine(DateUtils.parseDate("2014-12-24"), "234", "julien"))); + } + +} diff --git a/pom.xml b/pom.xml index 9325d620f16..7ed75fe5291 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ plugins/sonar-cpd-plugin plugins/sonar-l10n-en-plugin plugins/sonar-email-notifications-plugin + plugins/sonar-git-plugin plugins/sonar-xoo-plugin diff --git a/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java b/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java index 3a2cd265817..350135a0780 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java @@ -24,8 +24,8 @@ import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.api.batch.scm.BlameCommand.BlameResult; import org.sonar.api.batch.scm.BlameLine; -import org.sonar.api.batch.scm.ScmProvider.BlameResult; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; @@ -92,7 +92,7 @@ public final class ScmActivitySensor implements Sensor { filesToBlame.add(f); } } - configuration.provider().blame(fs, filesToBlame, new BlameResult() { + configuration.provider().blameCommand().blame(fs, filesToBlame, new BlameResult() { @Override public void add(InputFile file, List lines) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java new file mode 100644 index 00000000000..385b3528e54 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java @@ -0,0 +1,47 @@ +/* + * 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.api.batch.scm; + +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; + +import java.util.List; + +/** + * @since 5.0 + */ +public interface BlameCommand { + + /** + * Compute blame of the provided files. Computation can be done in parallel. + * If there is an error that prevent to blame a file then an exception should be raised. + */ + void blame(FileSystem fs, Iterable files, BlameResult result); + + /** + * Callback for the provider to report results of blame per file. + */ + public static interface BlameResult { + + void add(InputFile file, List lines); + + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java index 2303b90f794..c4362f05a9b 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java @@ -19,6 +19,11 @@ */ package org.sonar.api.batch.scm; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + import java.util.Date; /** @@ -99,4 +104,41 @@ public class BlameLine { this.date = null; } } + + // For testing purpose + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + BlameLine rhs = (BlameLine) obj; + return new EqualsBuilder() + .append(date, rhs.date) + .append(revision, rhs.revision) + .append(author, rhs.author) + .append(committer, rhs.committer) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(27, 45). + append(date) + .append(revision) + .append(author) + .append(committer) + .toHashCode(); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java index 2630f95b5e6..a32bec98538 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java @@ -21,42 +21,31 @@ package org.sonar.api.batch.scm; import org.sonar.api.BatchExtension; import org.sonar.api.batch.InstantiationStrategy; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.InputFile; import java.io.File; -import java.util.List; /** * @since 5.0 */ @InstantiationStrategy(InstantiationStrategy.PER_BATCH) -public interface ScmProvider extends BatchExtension { +public abstract class ScmProvider implements BatchExtension { /** * Unique identifier of the provider. Can be used in SCM URL to define the provider to use. */ - String key(); + public abstract String key(); /** * Does this provider able to manage files located in this directory. - * Used by autodetection. + * Used by autodetection. Not considered if user has forced the provider key. + * @return false by default */ - boolean supports(File baseDir); - - /** - * Compute blame of the provided files. Computation can be done in parallel. - * If there is an error that prevent to blame a file then an exception should be raised. - */ - void blame(FileSystem fs, Iterable files, BlameResult result); - - /** - * Callback for the provider to save results of blame per file. - */ - public static interface BlameResult { - - void add(InputFile file, List lines); + public boolean supports(File baseDir) { + return false; + } + public BlameCommand blameCommand() { + throw new UnsupportedOperationException("Blame command is not supported by " + key() + " provider"); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java index 23148378f35..42637fdd4cb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java @@ -52,10 +52,12 @@ public class DefaultIssue implements Issue { private final SensorStorage storage; public DefaultIssue() { + this.key = UUID.randomUUID().toString(); this.storage = null; } public DefaultIssue(SensorStorage storage) { + this.key = UUID.randomUUID().toString(); this.storage = storage; } @@ -155,9 +157,6 @@ public class DefaultIssue implements Issue { public void save() { Preconditions.checkNotNull(this.storage, "No persister on this object"); Preconditions.checkNotNull(this.ruleKey, "ruleKey is mandatory on issue"); - if (this.key == null) { - this.key = UUID.randomUUID().toString(); - } Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key"); storage.store(this); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java index 7d05c23f259..023adec8106 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java @@ -43,6 +43,10 @@ public class DefaultTestCase implements TestCase { private TestCase.Type type = Type.UNIT; private String stackTrace; + public DefaultTestCase() { + this.storage = null; + } + public DefaultTestCase(SensorStorage storage) { this.storage = storage; }