aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/test
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2020-08-25 14:25:04 -0500
committersonartech <sonartech@sonarsource.com>2020-08-28 20:06:52 +0000
commitda15a52568cbe80fe6ce70dd64aed3edd722af74 (patch)
treefee55b502d7ed02d28e1314c2be18630c5c8840a /sonar-scanner-engine/src/test
parent87bb21e6bb8510620ecea231229964a2163203b3 (diff)
downloadsonarqube-da15a52568cbe80fe6ce70dd64aed3edd722af74.tar.gz
sonarqube-da15a52568cbe80fe6ce70dd64aed3edd722af74.zip
SONAR-13792 Embed sonar-scm-svn
Diffstat (limited to 'sonar-scanner-engine/src/test')
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmSupportTest.java2
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/ChangedLinesComputerTest.java195
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/FindForkTest.java143
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java321
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnConfigurationTest.java68
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmProviderTest.java325
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmSupportTest.java51
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java135
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java68
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zipbin0 -> 23239 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zipbin0 -> 23239 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zipbin0 -> 23777 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zipbin0 -> 37668 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zipbin0 -> 32696 bytes
17 files changed, 1307 insertions, 1 deletions
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmSupportTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmSupportTest.java
index 803b02088be..10c33f9af38 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmSupportTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmSupportTest.java
@@ -27,7 +27,7 @@ public class GitScmSupportTest {
@Test
public void getClasses() {
- assertThat(GitScmSupport.getClasses()).hasSize(3);
+ assertThat(GitScmSupport.getObjects()).hasSize(3);
}
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/ChangedLinesComputerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/ChangedLinesComputerTest.java
new file mode 100644
index 00000000000..bef5eb6c57c
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/ChangedLinesComputerTest.java
@@ -0,0 +1,195 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ChangedLinesComputerTest {
+
+ private final Path rootBaseDir = Paths.get("/foo");
+ private final ChangedLinesComputer underTest = new ChangedLinesComputer(rootBaseDir, new HashSet<>(Arrays.asList(
+ rootBaseDir.resolve("sample1"),
+ rootBaseDir.resolve("sample2"),
+ rootBaseDir.resolve("sample3"),
+ rootBaseDir.resolve("sample4"))));
+
+ @Test
+ public void do_not_count_deleted_line() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -1 +0,0 @@\n"
+ + "-deleted line\n";
+
+ printDiff(example);
+ assertThat(underTest.changedLines()).isEmpty();
+ }
+
+ @Test
+ public void count_single_added_line() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -0,0 +1 @@\n"
+ + "+added line\n";
+
+ printDiff(example);
+ assertThat(underTest.changedLines()).isEqualTo(Collections.singletonMap(rootBaseDir.resolve("sample1"), singleton(1)));
+ }
+
+ @Test
+ public void count_multiple_added_lines() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -1 +1,3 @@\n"
+ + " same line\n"
+ + "+added line 1\n"
+ + "+added line 2\n";
+
+ printDiff(example);
+ assertThat(underTest.changedLines()).isEqualTo(Collections.singletonMap(rootBaseDir.resolve("sample1"), new HashSet<>(Arrays.asList(2, 3))));
+ }
+
+ @Test
+ public void handle_index_using_absolute_paths() throws IOException {
+ String example = "Index: /foo/sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -1 +1,3 @@\n"
+ + " same line\n"
+ + "+added line 1\n"
+ + "+added line 2\n";
+
+ printDiff(example);
+ assertThat(underTest.changedLines()).isEqualTo(Collections.singletonMap(rootBaseDir.resolve("sample1"), new HashSet<>(Arrays.asList(2, 3))));
+ }
+
+ @Test
+ public void compute_from_multiple_hunks() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- lao\t2002-02-21 23:30:39.942229878 -0800\n"
+ + "+++ tzu\t2002-02-21 23:30:50.442260588 -0800\n"
+ + "@@ -1,7 +1,6 @@\n"
+ + "-The Way that can be told of is not the eternal Way;\n"
+ + "-The name that can be named is not the eternal name.\n"
+ + " The Nameless is the origin of Heaven and Earth;\n"
+ + "-The Named is the mother of all things.\n"
+ + "+The named is the mother of all things.\n"
+ + "+\n"
+ + " Therefore let there always be non-being,\n"
+ + " so we may see their subtlety,\n"
+ + " And let there always be being,\n"
+ + "@@ -9,3 +8,6 @@\n"
+ + " The two are the same,\n"
+ + " But after they are produced,\n"
+ + " they have different names.\n"
+ + "+They both may be called deep and profound.\n"
+ + "+Deeper and more profound,\n"
+ + "+The door of all subtleties!\n";
+ printDiff(example);
+ assertThat(underTest.changedLines()).isEqualTo(Collections.singletonMap(rootBaseDir.resolve("sample1"), new HashSet<>(Arrays.asList(2, 3, 11, 12, 13))));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void crash_on_invalid_start_line_format() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -1 +x1,3 @@\n"
+ + " same line\n"
+ + "+added line 1\n"
+ + "+added line 2\n";
+
+ printDiff(example);
+ underTest.changedLines();
+ }
+
+ @Test
+ public void parse_diff_with_multiple_files() throws IOException {
+ String example = "Index: sample1\n"
+ + "===================================================================\n"
+ + "--- a/sample1\n"
+ + "+++ b/sample1\n"
+ + "@@ -1 +0,0 @@\n"
+ + "-deleted line\n"
+ + "Index: sample2\n"
+ + "===================================================================\n"
+ + "--- a/sample2\n"
+ + "+++ b/sample2\n"
+ + "@@ -0,0 +1 @@\n"
+ + "+added line\n"
+ + "Index: sample3\n"
+ + "===================================================================\n"
+ + "--- a/sample3\n"
+ + "+++ b/sample3\n"
+ + "@@ -0,0 +1,2 @@\n"
+ + "+added line 1\n"
+ + "+added line 2\n"
+ + "Index: sample3-not-included\n"
+ + "===================================================================\n"
+ + "--- a/sample3-not-included\n"
+ + "+++ b/sample3-not-included\n"
+ + "@@ -0,0 +1,2 @@\n"
+ + "+added line 1\n"
+ + "+added line 2\n"
+ + "Index: sample4\n"
+ + "===================================================================\n"
+ + "--- a/sample4\n"
+ + "+++ b/sample4\n"
+ + "@@ -1 +1,3 @@\n"
+ + " same line\n"
+ + "+added line 1\n"
+ + "+added line 2\n";
+
+ printDiff(example);
+ Map<Path, Set<Integer>> expected = new HashMap<>();
+ expected.put(rootBaseDir.resolve("sample2"), Collections.singleton(1));
+ expected.put(rootBaseDir.resolve("sample3"), new HashSet<>(Arrays.asList(1, 2)));
+ expected.put(rootBaseDir.resolve("sample4"), new HashSet<>(Arrays.asList(2, 3)));
+
+ assertThat(underTest.changedLines()).isEqualTo(expected);
+ }
+
+ private void printDiff(String unifiedDiff) throws IOException {
+ try (OutputStreamWriter writer = new OutputStreamWriter(underTest.receiver())) {
+ writer.write(unifiedDiff);
+ }
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/FindForkTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/FindForkTest.java
new file mode 100644
index 00000000000..5bbc7c4d109
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/FindForkTest.java
@@ -0,0 +1,143 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.tmatesoft.svn.core.SVNException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class FindForkTest {
+
+ @ClassRule
+ public static TemporaryFolder temp = new TemporaryFolder();
+
+ private static SvnTester svnTester;
+
+ private static Path trunk;
+ private static Path b1;
+ private static Path b2;
+
+ private FindFork findFork;
+
+ @BeforeClass
+ public static void before() throws IOException, SVNException {
+ svnTester = new SvnTester(temp.newFolder().toPath());
+
+ trunk = temp.newFolder("trunk").toPath();
+ svnTester.checkout(trunk, "trunk");
+ createAndCommitFile(trunk, "file-1-commit-in-trunk.xoo");
+ createAndCommitFile(trunk, "file-2-commit-in-trunk.xoo");
+ createAndCommitFile(trunk, "file-3-commit-in-trunk.xoo");
+ svnTester.checkout(trunk, "trunk");
+
+ svnTester.createBranch("b1");
+ b1 = temp.newFolder("branches", "b1").toPath();
+ svnTester.checkout(b1, "branches/b1");
+ createAndCommitFile(b1, "file-1-commit-in-b1.xoo");
+ createAndCommitFile(b1, "file-2-commit-in-b1.xoo");
+ createAndCommitFile(b1, "file-3-commit-in-b1.xoo");
+ svnTester.checkout(b1, "branches/b1");
+
+ svnTester.createBranch("branches/b1", "b2");
+ b2 = temp.newFolder("branches", "b2").toPath();
+ svnTester.checkout(b2, "branches/b2");
+
+ createAndCommitFile(b2, "file-1-commit-in-b2.xoo");
+ createAndCommitFile(b2, "file-2-commit-in-b2.xoo");
+ createAndCommitFile(b2, "file-3-commit-in-b2.xoo");
+ svnTester.checkout(b2, "branches/b2");
+ }
+
+ @Before
+ public void setUp() {
+ SvnConfiguration configurationMock = mock(SvnConfiguration.class);
+ findFork = new FindFork(configurationMock);
+ }
+
+ @Test
+ public void testEmptyBranch() throws SVNException, IOException {
+ svnTester.createBranch("empty");
+ Path empty = temp.newFolder("branches", "empty").toPath();
+
+ svnTester.checkout(empty, "branches/empty");
+ ForkPoint forkPoint = findFork.find(empty, "unknown");
+ assertThat(forkPoint).isNull();
+ }
+
+ @Test
+ public void returnNoDate() throws SVNException {
+ FindFork findFork = new FindFork(mock(SvnConfiguration.class)) {
+ @Override
+ public ForkPoint find(Path location, String referenceBranch) {
+ return null;
+ }
+ };
+
+ assertThat(findFork.findDate(Paths.get(""), "branch")).isNull();
+ }
+
+ @Test
+ public void testTrunk() throws SVNException {
+ ForkPoint forkPoint = findFork.find(trunk, "unknown");
+ assertThat(forkPoint).isNull();
+ }
+
+ @Test
+ public void testB1() throws SVNException {
+ ForkPoint forkPoint = findFork.find(b1, "trunk");
+ assertThat(forkPoint.commit()).isEqualTo("5");
+ }
+
+ @Test
+ public void testB2() throws SVNException {
+ ForkPoint forkPoint = findFork.find(b2, "branches/b1");
+ assertThat(forkPoint.commit()).isEqualTo("9");
+ }
+
+ @Test
+ public void testB2Date() throws SVNException {
+ assertThat(findFork.findDate(b2, "branches/b1")).isNotNull();
+ }
+
+ @Test
+ public void testB2FromTrunk() throws SVNException {
+ ForkPoint forkPoint = findFork.find(b2, "trunk");
+ assertThat(forkPoint.commit()).isEqualTo("5");
+ }
+
+ private static void createAndCommitFile(Path worktree, String filename, String content) throws IOException, SVNException {
+ svnTester.createFile(worktree, filename, content);
+ svnTester.add(worktree, filename);
+ svnTester.commit(worktree);
+ }
+
+ private static void createAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
+ createAndCommitFile(worktree, filename, filename + "\n");
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java
new file mode 100644
index 00000000000..930853b6a18
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java
@@ -0,0 +1,321 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.scm.BlameCommand.BlameInput;
+import org.sonar.api.batch.scm.BlameCommand.BlameOutput;
+import org.sonar.api.batch.scm.BlameLine;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
+import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNUpdateClient;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+import org.tmatesoft.svn.core.wc2.SvnCheckout;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(Parameterized.class)
+public class SvnBlameCommandTest {
+
+ /*
+ * Note about SONARSCSVN-11: The case of a project baseDir is in a subFolder of working copy is part of method tests by default
+ */
+
+ private static final String DUMMY_JAVA = "src/main/java/org/dummy/Dummy.java";
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private FileSystem fs;
+ private BlameInput input;
+ private String serverVersion;
+ private int wcVersion;
+
+ @Parameters(name = "SVN server version {0}, WC version {1}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][] {{"1.6", 10}, {"1.7", 29}, {"1.8", 31}, {"1.9", 31}});
+ }
+
+ public SvnBlameCommandTest(String serverVersion, int wcVersion) {
+ this.serverVersion = serverVersion;
+ this.wcVersion = wcVersion;
+ }
+
+ @Before
+ public void prepare() {
+ fs = mock(FileSystem.class);
+ input = mock(BlameInput.class);
+ when(input.fileSystem()).thenReturn(fs);
+ }
+
+ @Test
+ public void testParsingOfOutput() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(blameResult).blameResult(eq(inputFile), captor.capture());
+ List<BlameLine> result = captor.getValue();
+ assertThat(result).hasSize(27);
+ Date commitDate = new Date(1342691097393L);
+ BlameLine[] expected = IntStream.rangeClosed(1, 27).mapToObj(i -> new BlameLine().date(commitDate).revision("2").author("dgageot")).toArray(BlameLine[]::new);
+ assertThat(result).containsExactly(expected);
+ }
+
+ private File unzip(String repoName) throws IOException {
+ File repoDir = temp.newFolder();
+ try {
+ javaUnzip(Paths.get(this.getClass().getResource("test-repos").toURI()).resolve(serverVersion).resolve(repoName).toFile(), repoDir);
+ return repoDir;
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private File checkout(String scmUrl) throws Exception {
+ ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
+ ISVNAuthenticationManager isvnAuthenticationManager = SVNWCUtil.createDefaultAuthenticationManager(null, null, (char[]) null, false);
+ SVNClientManager svnClientManager = SVNClientManager.newInstance(options, isvnAuthenticationManager);
+ File out = temp.newFolder();
+ SVNUpdateClient updateClient = svnClientManager.getUpdateClient();
+ SvnCheckout co = updateClient.getOperationsFactory().createCheckout();
+ co.setUpdateLocksOnDemand(updateClient.isUpdateLocksOnDemand());
+ co.setSource(SvnTarget.fromURL(SVNURL.parseURIEncoded(scmUrl), SVNRevision.HEAD));
+ co.setSingleTarget(SvnTarget.fromFile(out));
+ co.setRevision(SVNRevision.HEAD);
+ co.setDepth(SVNDepth.INFINITY);
+ co.setAllowUnversionedObstructions(false);
+ co.setIgnoreExternals(updateClient.isIgnoreExternals());
+ co.setExternalsHandler(SvnCodec.externalsHandler(updateClient.getExternalsHandler()));
+ co.setTargetWorkingCopyFormat(wcVersion);
+ co.run();
+ return out;
+ }
+
+ @Test
+ public void testParsingOfOutputWithMergeHistory() throws Exception {
+ File repoDir = unzip("repo-svn-with-merge.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn/trunk");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(blameResult).blameResult(eq(inputFile), captor.capture());
+ List<BlameLine> result = captor.getValue();
+ assertThat(result).hasSize(27);
+ Date commitDate = new Date(1342691097393L);
+ Date revision6Date = new Date(1415262184300L);
+
+ BlameLine[] expected = IntStream.rangeClosed(1, 27).mapToObj(i -> {
+ if (i == 2 || i == 24) {
+ return new BlameLine().date(revision6Date).revision("6").author("henryju");
+ } else {
+ return new BlameLine().date(commitDate).revision("2").author("dgageot");
+ }
+ }).toArray(BlameLine[]::new);
+
+ assertThat(result).containsExactly(expected);
+ }
+
+ @Test
+ public void shouldNotFailIfFileContainsLocalModification() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Files.write(baseDir.toPath().resolve(DUMMY_JAVA), "\n//foo".getBytes(), StandardOpenOption.APPEND);
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ // SONARSCSVN-7
+ @Test
+ public void shouldNotFailOnWrongFilename() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA.toLowerCase())
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ @Test
+ public void shouldNotFailOnUncommitedFile() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ String relativePath = "src/main/java/org/dummy/Dummy2.java";
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", relativePath)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Files.write(baseDir.toPath().resolve(relativePath), "package org.dummy;\npublic class Dummy2 {}".getBytes());
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ @Test
+ public void shouldNotFailOnUncommitedDir() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ String relativePath = "src/main/java/org/dummy2/dummy/Dummy.java";
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", relativePath)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Path filepath = new File(baseDir, relativePath).toPath();
+ Files.createDirectories(filepath.getParent());
+ Files.write(filepath, "package org.dummy;\npublic class Dummy {}".getBytes());
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ private static void javaUnzip(File zip, File toDir) {
+ try {
+ try (ZipFile zipFile = new ZipFile(zip)) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ File to = new File(toDir, entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectories(to.toPath());
+ } else {
+ File parent = to.getParentFile();
+ if (parent != null) {
+ Files.createDirectories(parent.toPath());
+ }
+
+ Files.copy(zipFile.getInputStream(entry), to.toPath());
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to unzip " + zip + " to " + toDir, e);
+ }
+ }
+
+ private static String unixPath(File file) {
+ return file.getAbsolutePath().replace('\\', '/');
+ }
+
+ private SvnBlameCommand newSvnBlameCommand() {
+ return new SvnBlameCommand(mock(SvnConfiguration.class));
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnConfigurationTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnConfigurationTest.java
new file mode 100644
index 00000000000..7a765b28160
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnConfigurationTest.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.System2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class SvnConfigurationTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void sanityCheck() throws Exception {
+ MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, SvnConfiguration.getProperties()));
+ SvnConfiguration config = new SvnConfiguration(settings.asConfig());
+
+ assertThat(config.username()).isNull();
+ assertThat(config.password()).isNull();
+
+ settings.setProperty(SvnConfiguration.USER_PROP_KEY, "foo");
+ assertThat(config.username()).isEqualTo("foo");
+
+ settings.setProperty(SvnConfiguration.PASSWORD_PROP_KEY, "pwd");
+ assertThat(config.password()).isEqualTo("pwd");
+
+ settings.setProperty(SvnConfiguration.PASSPHRASE_PROP_KEY, "pass");
+ assertThat(config.passPhrase()).isEqualTo("pass");
+
+ assertThat(config.privateKey()).isNull();
+ File fakeKey = temp.newFile();
+ settings.setProperty(SvnConfiguration.PRIVATE_KEY_PATH_PROP_KEY, fakeKey.getAbsolutePath());
+ assertThat(config.privateKey()).isEqualTo(fakeKey);
+
+ settings.setProperty(SvnConfiguration.PRIVATE_KEY_PATH_PROP_KEY, "/not/exists");
+ try {
+ config.privateKey();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).hasMessageContaining("Unable to read private key from ");
+ }
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmProviderTest.java
new file mode 100644
index 00000000000..c815fdda0bf
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmProviderTest.java
@@ -0,0 +1,325 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+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.scm.ScmProvider;
+import org.tmatesoft.svn.core.SVNCancelException;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNLogClient;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SvnScmProviderTest {
+
+ // Sample content for unified diffs
+ // http://www.gnu.org/software/diffutils/manual/html_node/Example-Unified.html#Example-Unified
+ private static final String CONTENT_LAO = "The Way that can be told of is not the eternal Way;\n"
+ + "The name that can be named is not the eternal name.\n"
+ + "The Nameless is the origin of Heaven and Earth;\n"
+ + "The Named is the mother of all things.\n"
+ + "Therefore let there always be non-being,\n"
+ + " so we may see their subtlety,\n"
+ + "And let there always be being,\n"
+ + " so we may see their outcome.\n"
+ + "The two are the same,\n"
+ + "But after they are produced,\n"
+ + " they have different names.\n";
+
+ private static final String CONTENT_TZU = "The Nameless is the origin of Heaven and Earth;\n"
+ + "The named is the mother of all things.\n"
+ + "\n"
+ + "Therefore let there always be non-being,\n"
+ + " so we may see their subtlety,\n"
+ + "And let there always be being,\n"
+ + " so we may see their outcome.\n"
+ + "The two are the same,\n"
+ + "But after they are produced,\n"
+ + " they have different names.\n"
+ + "They both may be called deep and profound.\n"
+ + "Deeper and more profound,\n"
+ + "The door of all subtleties!";
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private FindFork findFork = mock(FindFork.class);
+ private SvnConfiguration config = mock(SvnConfiguration.class);
+ private SvnTester svnTester;
+
+ @Before
+ public void before() throws IOException, SVNException {
+ svnTester = new SvnTester(temp.newFolder().toPath());
+
+ Path worktree = temp.newFolder().toPath();
+ svnTester.checkout(worktree, "trunk");
+ createAndCommitFile(worktree, "file-in-first-commit.xoo");
+ }
+
+ @Test
+ public void sanityCheck() {
+ SvnBlameCommand blameCommand = new SvnBlameCommand(config);
+ SvnScmProvider svnScmProvider = new SvnScmProvider(config, blameCommand, findFork);
+ assertThat(svnScmProvider.key()).isEqualTo("svn");
+ assertThat(svnScmProvider.blameCommand()).isEqualTo(blameCommand);
+ }
+
+ @Test
+ public void testAutodetection() throws IOException {
+ ScmProvider scmBranchProvider = newScmProvider();
+
+ File baseDirEmpty = temp.newFolder();
+ assertThat(scmBranchProvider.supports(baseDirEmpty)).isFalse();
+
+ File svnBaseDir = temp.newFolder();
+ Files.createDirectory(svnBaseDir.toPath().resolve(".svn"));
+ assertThat(scmBranchProvider.supports(svnBaseDir)).isTrue();
+
+ File svnBaseDirSubFolder = temp.newFolder();
+ Files.createDirectory(svnBaseDirSubFolder.toPath().resolve(".svn"));
+ File projectBaseDir = new File(svnBaseDirSubFolder, "folder");
+ Files.createDirectory(projectBaseDir.toPath());
+ assertThat(scmBranchProvider.supports(projectBaseDir)).isTrue();
+ }
+
+ @Test
+ public void branchChangedFiles_and_lines_from_diverged() throws IOException, SVNException {
+ Path trunk = temp.newFolder().toPath();
+ svnTester.checkout(trunk, "trunk");
+ createAndCommitFile(trunk, "file-m1.xoo");
+ createAndCommitFile(trunk, "file-m2.xoo");
+ createAndCommitFile(trunk, "file-m3.xoo");
+ createAndCommitFile(trunk, "lao.txt", CONTENT_LAO);
+
+ // create branch from trunk
+ svnTester.createBranch("b1");
+
+ // still on trunk
+ appendToAndCommitFile(trunk, "file-m3.xoo");
+ createAndCommitFile(trunk, "file-m4.xoo");
+
+ Path b1 = temp.newFolder().toPath();
+ svnTester.checkout(b1, "branches/b1");
+ Files.createDirectories(b1.resolve("sub"));
+ createAndCommitFile(b1, "sub/file-b1.xoo");
+ appendToAndCommitFile(b1, "file-m1.xoo");
+ deleteAndCommitFile(b1, "file-m2.xoo");
+
+ createAndCommitFile(b1, "file-m5.xoo");
+ deleteAndCommitFile(b1, "file-m5.xoo");
+
+ svnCopyAndCommitFile(b1, "file-m1.xoo", "file-m1-copy.xoo");
+ appendToAndCommitFile(b1, "file-m1.xoo");
+
+ // modify file without committing it -> should not be included (think generated files)
+ svnTester.appendToFile(b1, "file-m3.xoo");
+
+ svnTester.update(b1);
+
+ Set<Path> changedFiles = newScmProvider().branchChangedFiles("trunk", b1);
+ assertThat(changedFiles)
+ .containsExactlyInAnyOrder(
+ b1.resolve("sub/file-b1.xoo"),
+ b1.resolve("file-m1.xoo"),
+ b1.resolve("file-m1-copy.xoo"));
+
+ // use a subset of changed files for .branchChangedLines to verify only requested files are returned
+ assertThat(changedFiles.remove(b1.resolve("sub/file-b1.xoo"))).isTrue();
+
+ // generate common sample diff
+ createAndCommitFile(b1, "lao.txt", CONTENT_TZU);
+ changedFiles.add(b1.resolve("lao.txt"));
+
+ // a file that should not yield any results
+ changedFiles.add(b1.resolve("nonexistent"));
+
+ // modify file without committing to it
+ svnTester.appendToFile(b1, "file-m1.xoo");
+
+ Map<Path, Set<Integer>> expected = new HashMap<>();
+ expected.put(b1.resolve("lao.txt"), new HashSet<>(Arrays.asList(2, 3, 11, 12, 13)));
+ expected.put(b1.resolve("file-m1.xoo"), new HashSet<>(Arrays.asList(2, 3, 4)));
+ expected.put(b1.resolve("file-m1-copy.xoo"), new HashSet<>(Arrays.asList(1, 2)));
+
+ assertThat(newScmProvider().branchChangedLines("trunk", b1, changedFiles))
+ .isEqualTo(expected);
+
+ assertThat(newScmProvider().branchChangedLines("trunk", b1, Collections.singleton(b1.resolve("nonexistent"))))
+ .isEmpty();
+ }
+
+ @Test
+ public void branchChangedFiles_should_return_empty_when_no_local_changes() throws IOException, SVNException {
+ Path b1 = temp.newFolder().toPath();
+ svnTester.createBranch("b1");
+ svnTester.checkout(b1, "branches/b1");
+
+ assertThat(newScmProvider().branchChangedFiles("b1", b1)).isEmpty();
+ }
+
+ @Test
+ public void branchChangedFiles_should_return_null_when_repo_nonexistent() throws IOException {
+ assertThat(newScmProvider().branchChangedFiles("trunk", temp.newFolder().toPath())).isNull();
+ }
+
+ @Test
+ public void branchChangedFiles_should_return_null_when_dir_nonexistent() {
+ assertThat(newScmProvider().branchChangedFiles("trunk", temp.getRoot().toPath().resolve("nonexistent"))).isNull();
+ }
+
+ @Test
+ public void branchChangedLines_should_return_null_when_repo_nonexistent() throws IOException {
+ assertThat(newScmProvider().branchChangedLines("trunk", temp.newFolder().toPath(), Collections.emptySet())).isNull();
+ }
+
+ @Test
+ public void branchChangedLines_should_return_null_when_dir_nonexistent() {
+ assertThat(newScmProvider().branchChangedLines("trunk", temp.getRoot().toPath().resolve("nonexistent"), Collections.emptySet())).isNull();
+ }
+
+ @Test
+ public void branchChangedLines_should_return_empty_when_no_local_changes() throws IOException, SVNException {
+ Path b1 = temp.newFolder().toPath();
+ svnTester.createBranch("b1");
+ svnTester.checkout(b1, "branches/b1");
+
+ assertThat(newScmProvider().branchChangedLines("b1", b1, Collections.emptySet())).isEmpty();
+ }
+
+ @Test
+ public void branchChangedLines_should_return_null_when_invalid_diff_format() throws IOException, SVNException {
+ Path b1 = temp.newFolder().toPath();
+ svnTester.createBranch("b1");
+ svnTester.checkout(b1, "branches/b1");
+
+ SvnScmProvider scmProvider = new SvnScmProvider(config, new SvnBlameCommand(config), findFork) {
+ @Override
+ ChangedLinesComputer newChangedLinesComputer(Path rootBaseDir, Set<Path> changedFiles) {
+ throw new IllegalStateException("crash");
+ }
+ };
+ assertThat(scmProvider.branchChangedLines("b1", b1, Collections.emptySet())).isNull();
+ }
+
+ @Test
+ public void forkDate_returns_null_if_no_fork_found() {
+ assertThat(new SvnScmProvider(config, new SvnBlameCommand(config), findFork).forkDate("branch", Paths.get(""))).isNull();
+ }
+
+ @Test
+ public void forkDate_returns_instant_if_fork_found() throws SVNException {
+ Path rootBaseDir = Paths.get("");
+ String referenceBranch = "branch";
+ Instant forkDate = Instant.ofEpochMilli(123456789L);
+ SvnScmProvider provider = new SvnScmProvider(config, new SvnBlameCommand(config), findFork);
+ when(findFork.findDate(rootBaseDir, referenceBranch)).thenReturn(forkDate);
+
+ assertThat(provider.forkDate(referenceBranch, rootBaseDir)).isEqualTo(forkDate);
+ }
+
+ @Test
+ public void forkDate_returns_null_if_exception_occurs() throws SVNException {
+ Path rootBaseDir = Paths.get("");
+ String referenceBranch = "branch";
+ SvnScmProvider provider = new SvnScmProvider(config, new SvnBlameCommand(config), findFork);
+ when(findFork.findDate(rootBaseDir, referenceBranch)).thenThrow(new SVNCancelException());
+
+ assertThat(provider.forkDate(referenceBranch, rootBaseDir)).isNull();
+ }
+
+ @Test
+ public void computeChangedPaths_should_not_crash_when_getRepositoryRootURL_getPath_is_empty() throws SVNException {
+ // verify assumptions about what SVNKit returns as svn root path for urls like http://svnserver/
+ assertThat(SVNURL.parseURIEncoded("http://svnserver/").getPath()).isEmpty();
+ assertThat(SVNURL.parseURIEncoded("http://svnserver").getPath()).isEmpty();
+
+ SVNClientManager svnClientManagerMock = mock(SVNClientManager.class);
+
+ SVNWCClient svnwcClientMock = mock(SVNWCClient.class);
+ when(svnClientManagerMock.getWCClient()).thenReturn(svnwcClientMock);
+
+ SVNLogClient svnLogClient = mock(SVNLogClient.class);
+ when(svnClientManagerMock.getLogClient()).thenReturn(svnLogClient);
+
+ SVNInfo svnInfoMock = mock(SVNInfo.class);
+ when(svnwcClientMock.doInfo(any(), any())).thenReturn(svnInfoMock);
+
+ // Simulate repository root on /, SVNKIT then returns an repository root url WITHOUT / at the end.
+ when(svnInfoMock.getRepositoryRootURL()).thenReturn(SVNURL.parseURIEncoded("http://svnserver"));
+ when(svnInfoMock.getURL()).thenReturn(SVNURL.parseURIEncoded("http://svnserver/myproject/trunk/"));
+
+ assertThat(SvnScmProvider.computeChangedPaths(Paths.get("/"), svnClientManagerMock)).isEmpty();
+ }
+
+ private void createAndCommitFile(Path worktree, String filename, String content) throws IOException, SVNException {
+ svnTester.createFile(worktree, filename, content);
+ svnTester.add(worktree, filename);
+ svnTester.commit(worktree);
+ }
+
+ private void createAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
+ createAndCommitFile(worktree, filename, filename + "\n");
+ }
+
+ private void appendToAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
+ svnTester.appendToFile(worktree, filename);
+ svnTester.commit(worktree);
+ }
+
+ private void deleteAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
+ svnTester.deleteFile(worktree, filename);
+ svnTester.commit(worktree);
+ }
+
+ private void svnCopyAndCommitFile(Path worktree, String src, String dst) throws SVNException {
+ svnTester.copy(worktree, src, dst);
+ svnTester.commit(worktree);
+ }
+
+ private SvnScmProvider newScmProvider() {
+ return new SvnScmProvider(config, new SvnBlameCommand(config), findFork);
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmSupportTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmSupportTest.java
new file mode 100644
index 00000000000..33c3bd37ac9
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmSupportTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.scm.svn.SvnScmSupport.newSvnClientManager;
+
+public class SvnScmSupportTest {
+ @Test
+ public void getExtensions() {
+ assertThat(SvnScmSupport.getObjects()).isNotEmpty();
+ }
+
+ @Test
+ public void newSvnClientManager_with_auth() {
+ SvnConfiguration config = mock(SvnConfiguration.class);
+ when(config.password()).thenReturn("password");
+ when(config.passPhrase()).thenReturn("passPhrase");
+ assertThat(newSvnClientManager(config)).isNotNull();
+ }
+
+ @Test
+ public void newSvnClientManager_without_auth() {
+ SvnConfiguration config = mock(SvnConfiguration.class);
+ assertThat(config.password()).isNull();
+ assertThat(config.passPhrase()).isNull();
+ assertThat(newSvnClientManager(config)).isNotNull();
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java
new file mode 100644
index 00000000000..ec00ca50b3c
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNCopyClient;
+import org.tmatesoft.svn.core.wc.SVNCopySource;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNUpdateClient;
+import org.tmatesoft.svn.core.wc2.SvnList;
+import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
+import org.tmatesoft.svn.core.wc2.SvnRemoteMkDir;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+public class SvnTester {
+ private final SVNClientManager manager = SVNClientManager.newInstance(new SvnOperationFactory());
+
+ private final SVNURL localRepository;
+
+ public SvnTester(Path root) throws SVNException, IOException {
+ localRepository = SVNRepositoryFactory.createLocalRepository(root.toFile(), false, false);
+ mkdir("trunk");
+ mkdir("branches");
+ }
+
+ private void mkdir(String relpath) throws IOException, SVNException {
+ SvnRemoteMkDir remoteMkDir = manager.getOperationFactory().createRemoteMkDir();
+ remoteMkDir.addTarget(SvnTarget.fromURL(localRepository.appendPath(relpath, false)));
+ remoteMkDir.run();
+ }
+
+ public void createBranch(String branchName) throws IOException, SVNException {
+ SVNCopyClient copyClient = manager.getCopyClient();
+ SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath("trunk", false));
+ copyClient.doCopy(new SVNCopySource[] {source}, localRepository.appendPath("branches/" + branchName, false), false, false, true, "Create branch", null);
+ }
+
+ public void createBranch(String branchSource, String branchName) throws IOException, SVNException {
+ SVNCopyClient copyClient = manager.getCopyClient();
+ SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath(branchSource, false));
+ copyClient.doCopy(new SVNCopySource[] {source}, localRepository.appendPath("branches/" + branchName, false), false, false, true, "Create branch", null);
+ }
+
+ public void checkout(Path worktree, String path) throws SVNException {
+ SVNUpdateClient updateClient = manager.getUpdateClient();
+ updateClient.doCheckout(localRepository.appendPath(path, false),
+ worktree.toFile(), null, null, SVNDepth.INFINITY, false);
+ }
+
+ public void add(Path worktree, String filename) throws SVNException {
+ manager.getWCClient().doAdd(worktree.resolve(filename).toFile(), true, false, false, SVNDepth.INFINITY, false, false, true);
+ }
+
+ public void copy(Path worktree, String src, String dst) throws SVNException {
+ SVNCopyClient copyClient = manager.getCopyClient();
+ SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, worktree.resolve(src).toFile());
+ copyClient.doCopy(new SVNCopySource[]{source}, worktree.resolve(dst).toFile(), false, false, true);
+ }
+
+ public void commit(Path worktree) throws SVNException {
+ manager.getCommitClient().doCommit(new File[] {worktree.toFile()}, false, "commit " + worktree, null, null, false, false, SVNDepth.INFINITY);
+ }
+
+ public void update(Path worktree) throws SVNException {
+ manager.getUpdateClient().doUpdate(new File[] {worktree.toFile()}, SVNRevision.HEAD, SVNDepth.INFINITY, false, false);
+ }
+
+ public Collection<String> list(String... paths) throws SVNException {
+ Set<String> results = new HashSet<>();
+
+ SvnList list = manager.getOperationFactory().createList();
+ if (paths.length == 0) {
+ list.addTarget(SvnTarget.fromURL(localRepository));
+ } else {
+ for (String path : paths) {
+ list.addTarget(SvnTarget.fromURL(localRepository.appendPath(path, false)));
+ }
+ }
+ list.setDepth(SVNDepth.INFINITY);
+ list.setReceiver((svnTarget, svnDirEntry) -> {
+ String path = svnDirEntry.getRelativePath();
+ if (!path.isEmpty()) {
+ results.add(path);
+ }
+ });
+ list.run();
+
+ return results;
+ }
+
+ public void createFile(Path worktree, String filename, String content) throws IOException {
+ Files.write(worktree.resolve(filename), content.getBytes());
+ }
+
+ public void createFile(Path worktree, String filename) throws IOException {
+ createFile(worktree, filename, filename + "\n");
+ }
+
+ public void appendToFile(Path worktree, String filename) throws IOException {
+ Files.write(worktree.resolve(filename), (filename + "\n").getBytes(), StandardOpenOption.APPEND);
+ }
+
+ public void deleteFile(Path worktree, String filename) throws SVNException {
+ manager.getWCClient().doDelete(worktree.resolve(filename).toFile(), false, false);
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java
new file mode 100644
index 00000000000..0740bb3960f
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * 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 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.tmatesoft.svn.core.SVNException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SvnTesterTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private SvnTester tester;
+
+ @Before
+ public void before() throws IOException, SVNException {
+ tester = new SvnTester(temp.newFolder().toPath());
+ }
+
+ @Test
+ public void test_init() throws SVNException {
+ assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches");
+ }
+
+ @Test
+ public void test_add_and_commit() throws IOException, SVNException {
+ assertThat(tester.list("trunk")).isEmpty();
+
+ Path worktree = temp.newFolder().toPath();
+ tester.checkout(worktree, "trunk");
+ tester.createFile(worktree, "file1");
+
+ tester.add(worktree, "file1");
+ tester.commit(worktree);
+
+ assertThat(tester.list("trunk")).containsOnly("file1");
+ }
+
+ @Test
+ public void test_createBranch() throws IOException, SVNException {
+ tester.createBranch("b1");
+ assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches", "branches/b1");
+ assertThat(tester.list("branches")).containsOnly("b1");
+ }
+}
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..d3c3ee5fa1e
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip
new file mode 100644
index 00000000000..291d4fb86f7
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..d3c3ee5fa1e
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip
new file mode 100644
index 00000000000..291d4fb86f7
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..852a05e4574
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip
new file mode 100644
index 00000000000..f31352c5e03
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..ca672fd6681
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip
new file mode 100644
index 00000000000..39b241cd698
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip
Binary files differ