diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2020-08-25 14:25:04 -0500 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-08-28 20:06:52 +0000 |
commit | da15a52568cbe80fe6ce70dd64aed3edd722af74 (patch) | |
tree | fee55b502d7ed02d28e1314c2be18630c5c8840a /sonar-scanner-engine/src/test | |
parent | 87bb21e6bb8510620ecea231229964a2163203b3 (diff) | |
download | sonarqube-da15a52568cbe80fe6ce70dd64aed3edd722af74.tar.gz sonarqube-da15a52568cbe80fe6ce70dd64aed3edd722af74.zip |
SONAR-13792 Embed sonar-scm-svn
Diffstat (limited to 'sonar-scanner-engine/src/test')
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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew 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 |