You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SvnScmProviderTest.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.scm.svn;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.nio.file.Files;
  24. import java.nio.file.Path;
  25. import java.nio.file.Paths;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.HashSet;
  30. import java.util.Map;
  31. import java.util.Set;
  32. import org.junit.Before;
  33. import org.junit.Rule;
  34. import org.junit.Test;
  35. import org.junit.rules.TemporaryFolder;
  36. import org.sonar.api.batch.scm.ScmProvider;
  37. import org.tmatesoft.svn.core.SVNException;
  38. import org.tmatesoft.svn.core.SVNURL;
  39. import org.tmatesoft.svn.core.wc.SVNClientManager;
  40. import org.tmatesoft.svn.core.wc.SVNInfo;
  41. import org.tmatesoft.svn.core.wc.SVNLogClient;
  42. import org.tmatesoft.svn.core.wc.SVNWCClient;
  43. import static org.assertj.core.api.Assertions.assertThat;
  44. import static org.mockito.Mockito.any;
  45. import static org.mockito.Mockito.mock;
  46. import static org.mockito.Mockito.when;
  47. public class SvnScmProviderTest {
  48. // Sample content for unified diffs
  49. // http://www.gnu.org/software/diffutils/manual/html_node/Example-Unified.html#Example-Unified
  50. private static final String CONTENT_LAO = "The Way that can be told of is not the eternal Way;\n"
  51. + "The name that can be named is not the eternal name.\n"
  52. + "The Nameless is the origin of Heaven and Earth;\n"
  53. + "The Named is the mother of all things.\n"
  54. + "Therefore let there always be non-being,\n"
  55. + " so we may see their subtlety,\n"
  56. + "And let there always be being,\n"
  57. + " so we may see their outcome.\n"
  58. + "The two are the same,\n"
  59. + "But after they are produced,\n"
  60. + " they have different names.\n";
  61. private static final String CONTENT_TZU = "The Nameless is the origin of Heaven and Earth;\n"
  62. + "The named is the mother of all things.\n"
  63. + "\n"
  64. + "Therefore let there always be non-being,\n"
  65. + " so we may see their subtlety,\n"
  66. + "And let there always be being,\n"
  67. + " so we may see their outcome.\n"
  68. + "The two are the same,\n"
  69. + "But after they are produced,\n"
  70. + " they have different names.\n"
  71. + "They both may be called deep and profound.\n"
  72. + "Deeper and more profound,\n"
  73. + "The door of all subtleties!";
  74. @Rule
  75. public TemporaryFolder temp = new TemporaryFolder();
  76. private SvnConfiguration config = mock(SvnConfiguration.class);
  77. private SvnTester svnTester;
  78. @Before
  79. public void before() throws IOException, SVNException {
  80. svnTester = new SvnTester(temp.newFolder().toPath());
  81. Path worktree = temp.newFolder().toPath();
  82. svnTester.checkout(worktree, "trunk");
  83. createAndCommitFile(worktree, "file-in-first-commit.xoo");
  84. }
  85. @Test
  86. public void sanityCheck() {
  87. SvnBlameCommand blameCommand = new SvnBlameCommand(config);
  88. SvnScmProvider svnScmProvider = new SvnScmProvider(config, blameCommand);
  89. assertThat(svnScmProvider.key()).isEqualTo("svn");
  90. assertThat(svnScmProvider.blameCommand()).isEqualTo(blameCommand);
  91. }
  92. @Test
  93. public void testAutodetection() throws IOException {
  94. ScmProvider scmBranchProvider = newScmProvider();
  95. File baseDirEmpty = temp.newFolder();
  96. assertThat(scmBranchProvider.supports(baseDirEmpty)).isFalse();
  97. File svnBaseDir = temp.newFolder();
  98. Files.createDirectory(svnBaseDir.toPath().resolve(".svn"));
  99. assertThat(scmBranchProvider.supports(svnBaseDir)).isTrue();
  100. File svnBaseDirSubFolder = temp.newFolder();
  101. Files.createDirectory(svnBaseDirSubFolder.toPath().resolve(".svn"));
  102. File projectBaseDir = new File(svnBaseDirSubFolder, "folder");
  103. Files.createDirectory(projectBaseDir.toPath());
  104. assertThat(scmBranchProvider.supports(projectBaseDir)).isTrue();
  105. }
  106. @Test
  107. public void branchChangedFiles_and_lines_from_diverged() throws IOException, SVNException {
  108. Path trunk = temp.newFolder().toPath();
  109. svnTester.checkout(trunk, "trunk");
  110. createAndCommitFile(trunk, "file-m1.xoo");
  111. createAndCommitFile(trunk, "file-m2.xoo");
  112. createAndCommitFile(trunk, "file-m3.xoo");
  113. createAndCommitFile(trunk, "lao.txt", CONTENT_LAO);
  114. // create branch from trunk
  115. svnTester.createBranch("b1");
  116. // still on trunk
  117. appendToAndCommitFile(trunk, "file-m3.xoo");
  118. createAndCommitFile(trunk, "file-m4.xoo");
  119. Path b1 = temp.newFolder().toPath();
  120. svnTester.checkout(b1, "branches/b1");
  121. Files.createDirectories(b1.resolve("sub"));
  122. createAndCommitFile(b1, "sub/file-b1.xoo");
  123. appendToAndCommitFile(b1, "file-m1.xoo");
  124. deleteAndCommitFile(b1, "file-m2.xoo");
  125. createAndCommitFile(b1, "file-m5.xoo");
  126. deleteAndCommitFile(b1, "file-m5.xoo");
  127. svnCopyAndCommitFile(b1, "file-m1.xoo", "file-m1-copy.xoo");
  128. appendToAndCommitFile(b1, "file-m1.xoo");
  129. // modify file without committing it -> should not be included (think generated files)
  130. svnTester.appendToFile(b1, "file-m3.xoo");
  131. svnTester.update(b1);
  132. Set<Path> changedFiles = newScmProvider().branchChangedFiles("trunk", b1);
  133. assertThat(changedFiles)
  134. .containsExactlyInAnyOrder(
  135. b1.resolve("sub/file-b1.xoo"),
  136. b1.resolve("file-m1.xoo"),
  137. b1.resolve("file-m1-copy.xoo"));
  138. // use a subset of changed files for .branchChangedLines to verify only requested files are returned
  139. assertThat(changedFiles.remove(b1.resolve("sub/file-b1.xoo"))).isTrue();
  140. // generate common sample diff
  141. createAndCommitFile(b1, "lao.txt", CONTENT_TZU);
  142. changedFiles.add(b1.resolve("lao.txt"));
  143. // a file that should not yield any results
  144. changedFiles.add(b1.resolve("nonexistent"));
  145. // modify file without committing to it
  146. svnTester.appendToFile(b1, "file-m1.xoo");
  147. Map<Path, Set<Integer>> expected = new HashMap<>();
  148. expected.put(b1.resolve("lao.txt"), new HashSet<>(Arrays.asList(2, 3, 11, 12, 13)));
  149. expected.put(b1.resolve("file-m1.xoo"), new HashSet<>(Arrays.asList(2, 3, 4)));
  150. expected.put(b1.resolve("file-m1-copy.xoo"), new HashSet<>(Arrays.asList(1, 2)));
  151. assertThat(newScmProvider().branchChangedLines("trunk", b1, changedFiles))
  152. .isEqualTo(expected);
  153. assertThat(newScmProvider().branchChangedLines("trunk", b1, Collections.singleton(b1.resolve("nonexistent"))))
  154. .isEmpty();
  155. }
  156. @Test
  157. public void branchChangedFiles_should_return_empty_when_no_local_changes() throws IOException, SVNException {
  158. Path b1 = temp.newFolder().toPath();
  159. svnTester.createBranch("b1");
  160. svnTester.checkout(b1, "branches/b1");
  161. assertThat(newScmProvider().branchChangedFiles("b1", b1)).isEmpty();
  162. }
  163. @Test
  164. public void branchChangedFiles_should_return_null_when_repo_nonexistent() throws IOException {
  165. assertThat(newScmProvider().branchChangedFiles("trunk", temp.newFolder().toPath())).isNull();
  166. }
  167. @Test
  168. public void branchChangedFiles_should_return_null_when_dir_nonexistent() {
  169. assertThat(newScmProvider().branchChangedFiles("trunk", temp.getRoot().toPath().resolve("nonexistent"))).isNull();
  170. }
  171. @Test
  172. public void branchChangedLines_should_return_null_when_repo_nonexistent() throws IOException {
  173. assertThat(newScmProvider().branchChangedLines("trunk", temp.newFolder().toPath(), Collections.emptySet())).isNull();
  174. }
  175. @Test
  176. public void branchChangedLines_should_return_null_when_dir_nonexistent() {
  177. assertThat(newScmProvider().branchChangedLines("trunk", temp.getRoot().toPath().resolve("nonexistent"), Collections.emptySet())).isNull();
  178. }
  179. @Test
  180. public void branchChangedLines_should_return_empty_when_no_local_changes() throws IOException, SVNException {
  181. Path b1 = temp.newFolder().toPath();
  182. svnTester.createBranch("b1");
  183. svnTester.checkout(b1, "branches/b1");
  184. assertThat(newScmProvider().branchChangedLines("b1", b1, Collections.emptySet())).isEmpty();
  185. }
  186. @Test
  187. public void branchChangedLines_should_return_null_when_invalid_diff_format() throws IOException, SVNException {
  188. Path b1 = temp.newFolder().toPath();
  189. svnTester.createBranch("b1");
  190. svnTester.checkout(b1, "branches/b1");
  191. SvnScmProvider scmProvider = new SvnScmProvider(config, new SvnBlameCommand(config)) {
  192. @Override
  193. ChangedLinesComputer newChangedLinesComputer(Path rootBaseDir, Set<Path> changedFiles) {
  194. throw new IllegalStateException("crash");
  195. }
  196. };
  197. assertThat(scmProvider.branchChangedLines("b1", b1, Collections.emptySet())).isNull();
  198. }
  199. @Test
  200. public void forkDate_returns_null() throws SVNException {
  201. SvnScmProvider provider = new SvnScmProvider(config, new SvnBlameCommand(config));
  202. assertThat(provider.forkDate("", Paths.get(""))).isNull();
  203. }
  204. @Test
  205. public void computeChangedPaths_should_not_crash_when_getRepositoryRootURL_getPath_is_empty() throws SVNException {
  206. // verify assumptions about what SVNKit returns as svn root path for urls like http://svnserver/
  207. assertThat(SVNURL.parseURIEncoded("http://svnserver/").getPath()).isEmpty();
  208. assertThat(SVNURL.parseURIEncoded("http://svnserver").getPath()).isEmpty();
  209. SVNClientManager svnClientManagerMock = mock(SVNClientManager.class);
  210. SVNWCClient svnwcClientMock = mock(SVNWCClient.class);
  211. when(svnClientManagerMock.getWCClient()).thenReturn(svnwcClientMock);
  212. SVNLogClient svnLogClient = mock(SVNLogClient.class);
  213. when(svnClientManagerMock.getLogClient()).thenReturn(svnLogClient);
  214. SVNInfo svnInfoMock = mock(SVNInfo.class);
  215. when(svnwcClientMock.doInfo(any(), any())).thenReturn(svnInfoMock);
  216. // Simulate repository root on /, SVNKIT then returns an repository root url WITHOUT / at the end.
  217. when(svnInfoMock.getRepositoryRootURL()).thenReturn(SVNURL.parseURIEncoded("http://svnserver"));
  218. when(svnInfoMock.getURL()).thenReturn(SVNURL.parseURIEncoded("http://svnserver/myproject/trunk/"));
  219. assertThat(SvnScmProvider.computeChangedPaths(Paths.get("/"), svnClientManagerMock)).isEmpty();
  220. }
  221. private void createAndCommitFile(Path worktree, String filename, String content) throws IOException, SVNException {
  222. svnTester.createFile(worktree, filename, content);
  223. svnTester.add(worktree, filename);
  224. svnTester.commit(worktree);
  225. }
  226. private void createAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
  227. createAndCommitFile(worktree, filename, filename + "\n");
  228. }
  229. private void appendToAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
  230. svnTester.appendToFile(worktree, filename);
  231. svnTester.commit(worktree);
  232. }
  233. private void deleteAndCommitFile(Path worktree, String filename) throws SVNException {
  234. svnTester.deleteFile(worktree, filename);
  235. svnTester.commit(worktree);
  236. }
  237. private void svnCopyAndCommitFile(Path worktree, String src, String dst) throws SVNException {
  238. svnTester.copy(worktree, src, dst);
  239. svnTester.commit(worktree);
  240. }
  241. private SvnScmProvider newScmProvider() {
  242. return new SvnScmProvider(config, new SvnBlameCommand(config));
  243. }
  244. }