1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.history; 25 26 import org.junit.jupiter.api.AfterEach; 27 import org.junit.jupiter.api.BeforeEach; 28 import org.junit.jupiter.api.Test; 29 import org.opengrok.indexer.condition.EnabledForRepository; 30 import org.opengrok.indexer.configuration.RuntimeEnvironment; 31 import org.opengrok.indexer.util.Executor; 32 import org.opengrok.indexer.util.TestRepository; 33 34 import java.io.File; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.net.URISyntaxException; 38 import java.nio.file.Paths; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.stream.Collectors; 44 45 import static org.junit.jupiter.api.Assertions.assertEquals; 46 import static org.junit.jupiter.api.Assertions.assertNotNull; 47 import static org.junit.jupiter.api.Assertions.assertNotSame; 48 import static org.junit.jupiter.api.Assertions.assertThrows; 49 import static org.junit.jupiter.api.Assertions.assertTrue; 50 import static org.opengrok.indexer.condition.RepositoryInstalled.Type.MERCURIAL; 51 52 /** 53 * Tests for MercurialRepository. 54 */ 55 @EnabledForRepository(MERCURIAL) 56 public class MercurialRepositoryTest { 57 58 /** 59 * Revision numbers present in the Mercurial test repository, in the order 60 * they are supposed to be returned from getHistory(), that is latest 61 * changeset first. 62 */ 63 private static final String[] REVISIONS = { 64 "9:8b340409b3a8", 65 "8:6a8c423f5624", "7:db1394c05268", "6:e386b51ddbcc", 66 "5:8706402863c6", "4:e494d67af12f", "3:2058725c1470", 67 "2:585a1b3f2efb", "1:f24a5fd7a85d", "0:816b6279ae9c" 68 }; 69 70 // extra revisions for branch test 71 private static final String[] REVISIONS_extra_branch = { 72 "10:c4518ca0c841" 73 }; 74 75 // novel.txt (or its ancestors) existed only since revision 3 76 private static final String[] REVISIONS_novel = { 77 "9:8b340409b3a8", 78 "8:6a8c423f5624", "7:db1394c05268", "6:e386b51ddbcc", 79 "5:8706402863c6", "4:e494d67af12f", "3:2058725c1470" 80 }; 81 82 private TestRepository repository; 83 84 /** 85 * Set up a test repository. Should be called by the tests that need it. The 86 * test repository will be destroyed automatically when the test finishes. 87 */ setUpTestRepository()88 private void setUpTestRepository() throws IOException, URISyntaxException { 89 repository = new TestRepository(); 90 repository.create(getClass().getResource("/repositories")); 91 } 92 93 @BeforeEach setup()94 public void setup() throws IOException, URISyntaxException { 95 setUpTestRepository(); 96 } 97 98 @AfterEach tearDown()99 public void tearDown() { 100 if (repository != null) { 101 repository.destroy(); 102 repository = null; 103 } 104 } 105 106 @Test testGetHistory()107 public void testGetHistory() throws Exception { 108 File root = new File(repository.getSourceRoot(), "mercurial"); 109 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 110 History hist = mr.getHistory(root); 111 List<HistoryEntry> entries = hist.getHistoryEntries(); 112 assertEquals(REVISIONS.length, entries.size()); 113 for (int i = 0; i < entries.size(); i++) { 114 HistoryEntry e = entries.get(i); 115 assertEquals(REVISIONS[i], e.getRevision()); 116 assertNotNull(e.getAuthor()); 117 assertNotNull(e.getDate()); 118 assertNotNull(e.getFiles()); 119 assertNotNull(e.getMessage()); 120 } 121 } 122 123 @Test testGetHistorySubdir()124 public void testGetHistorySubdir() throws Exception { 125 File root = new File(repository.getSourceRoot(), "mercurial"); 126 127 // Add a subdirectory with some history. 128 runHgCommand(root, "import", 129 Paths.get(getClass().getResource("/history/hg-export-subdir.txt").toURI()).toString()); 130 131 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 132 History hist = mr.getHistory(new File(root, "subdir")); 133 List<HistoryEntry> entries = hist.getHistoryEntries(); 134 assertEquals(1, entries.size()); 135 } 136 137 /** 138 * Test that subset of changesets can be extracted based on penultimate 139 * revision number. This works for directories only. 140 * @throws Exception 141 */ 142 @Test testGetHistoryPartial()143 public void testGetHistoryPartial() throws Exception { 144 File root = new File(repository.getSourceRoot(), "mercurial"); 145 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 146 // Get all but the oldest revision. 147 History hist = mr.getHistory(root, REVISIONS[REVISIONS.length - 1]); 148 List<HistoryEntry> entries = hist.getHistoryEntries(); 149 assertEquals(REVISIONS.length - 1, entries.size()); 150 for (int i = 0; i < entries.size(); i++) { 151 HistoryEntry e = entries.get(i); 152 assertEquals(REVISIONS[i], e.getRevision()); 153 assertNotNull(e.getAuthor()); 154 assertNotNull(e.getDate()); 155 assertNotNull(e.getFiles()); 156 assertNotNull(e.getMessage()); 157 } 158 } 159 160 /** 161 * Run Mercurial command. 162 * @param reposRoot directory of the repository root 163 * @param args {@code hg} command arguments 164 */ runHgCommand(File reposRoot, String... args)165 public static void runHgCommand(File reposRoot, String... args) { 166 List<String> cmdargs = new ArrayList<>(); 167 MercurialRepository repo = new MercurialRepository(); 168 169 cmdargs.add(repo.getRepoCommand()); 170 cmdargs.addAll(Arrays.asList(args)); 171 172 Executor exec = new Executor(cmdargs, reposRoot); 173 int exitCode = exec.exec(); 174 assertEquals(0, exitCode, "hg command '" + cmdargs.toString() + "' failed." 175 + "\nexit code: " + exitCode 176 + "\nstdout:\n" + exec.getOutputString() 177 + "\nstderr:\n" + exec.getErrorString()); 178 } 179 180 /** 181 * Test that history of branched repository contains changesets of the 182 * default branch as well. 183 * @throws Exception 184 */ 185 @Test testGetHistoryBranch()186 public void testGetHistoryBranch() throws Exception { 187 File root = new File(repository.getSourceRoot(), "mercurial"); 188 189 // Branch the repo and add one changeset. 190 runHgCommand(root, "unbundle", 191 Paths.get(getClass().getResource("/history/hg-branch.bundle").toURI()).toString()); 192 // Switch to the branch. 193 runHgCommand(root, "update", "mybranch"); 194 195 // Since the above hg commands change the active branch the repository 196 // needs to be initialized here so that its branch matches. 197 MercurialRepository mr 198 = (MercurialRepository) RepositoryFactory.getRepository(root); 199 200 // Get all revisions. 201 History hist = mr.getHistory(root); 202 List<HistoryEntry> entries = hist.getHistoryEntries(); 203 List<String> both = new ArrayList<>(REVISIONS.length 204 + REVISIONS_extra_branch.length); 205 Collections.addAll(both, REVISIONS_extra_branch); 206 Collections.addAll(both, REVISIONS); 207 String[] revs = both.toArray(new String[0]); 208 assertEquals(revs.length, entries.size()); 209 // Ideally we should check that the last revision is branched but 210 // there is currently no provision for that in HistoryEntry object. 211 for (int i = 0; i < entries.size(); i++) { 212 HistoryEntry e = entries.get(i); 213 assertEquals(revs[i], e.getRevision()); 214 assertNotNull(e.getAuthor()); 215 assertNotNull(e.getDate()); 216 assertNotNull(e.getFiles()); 217 assertNotNull(e.getMessage()); 218 } 219 220 // Get revisions starting with given changeset before the repo was branched. 221 hist = mr.getHistory(root, "8:6a8c423f5624"); 222 entries = hist.getHistoryEntries(); 223 assertEquals(2, entries.size()); 224 assertEquals(REVISIONS_extra_branch[0], entries.get(0).getRevision()); 225 assertEquals(REVISIONS[0], entries.get(1).getRevision()); 226 } 227 228 /** 229 * Test that contents of last revision of a text file match expected content. 230 * @throws java.lang.Exception 231 */ 232 @Test testGetHistoryGet()233 public void testGetHistoryGet() throws Exception { 234 File root = new File(repository.getSourceRoot(), "mercurial"); 235 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 236 String exp_str = "This will be a first novel of mine.\n" 237 + "\n" 238 + "Chapter 1.\n" 239 + "\n" 240 + "Let's write some words. It began like this:\n" 241 + "\n" 242 + "...\n"; 243 byte[] buffer = new byte[1024]; 244 245 InputStream input = mr.getHistoryGet(root.getCanonicalPath(), 246 "novel.txt", REVISIONS[0]); 247 assertNotNull(input); 248 249 String str = ""; 250 int len; 251 while ((len = input.read(buffer)) > 0) { 252 str += new String(buffer, 0, len); 253 } 254 assertNotSame(str.length(), 0); 255 assertEquals(exp_str, str); 256 } 257 258 /** 259 * Test that it is possible to get contents of multiple revisions of a file. 260 * @throws java.lang.Exception 261 */ 262 @Test testgetHistoryGetForAll()263 public void testgetHistoryGetForAll() throws Exception { 264 File root = new File(repository.getSourceRoot(), "mercurial"); 265 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 266 267 for (String rev : REVISIONS_novel) { 268 InputStream input = mr.getHistoryGet(root.getCanonicalPath(), 269 "novel.txt", rev); 270 assertNotNull(input); 271 } 272 } 273 274 /** 275 * Test that {@code getHistoryGet()} returns historical contents of renamed 276 * file. 277 * @throws java.lang.Exception 278 */ 279 @Test testGetHistoryGetRenamed()280 public void testGetHistoryGetRenamed() throws Exception { 281 File root = new File(repository.getSourceRoot(), "mercurial"); 282 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 283 String exp_str = "This is totally plaintext file.\n"; 284 byte[] buffer = new byte[1024]; 285 286 /* 287 * In our test repository the file was renamed twice since 288 * revision 3. 289 */ 290 InputStream input = mr.getHistoryGet(root.getCanonicalPath(), 291 "novel.txt", "3"); 292 assert (input != null); 293 int len = input.read(buffer); 294 assert (len != -1); 295 String str = new String(buffer, 0, len); 296 assert (str.compareTo(exp_str) == 0); 297 } 298 299 /** 300 * Test that {@code getHistory()} throws an exception if the revision 301 * argument doesn't match any of the revisions in the history. 302 * @throws java.lang.Exception 303 */ 304 @Test testGetHistoryWithNoSuchRevision()305 public void testGetHistoryWithNoSuchRevision() throws Exception { 306 File root = new File(repository.getSourceRoot(), "mercurial"); 307 MercurialRepository mr = (MercurialRepository) RepositoryFactory.getRepository(root); 308 309 // Get the sequence number and the hash from one of the revisions. 310 String[] revisionParts = REVISIONS[1].split(":"); 311 assertEquals(2, revisionParts.length); 312 int number = Integer.parseInt(revisionParts[0]); 313 String hash = revisionParts[1]; 314 315 // Construct a revision identifier that doesn't exist. 316 String constructedRevision = (number + 1) + ":" + hash; 317 assertThrows(HistoryException.class, () -> mr.getHistory(root, constructedRevision)); 318 } 319 320 @Test testGetHistorySinceTillNullNull()321 void testGetHistorySinceTillNullNull() throws Exception { 322 File root = new File(repository.getSourceRoot(), "mercurial"); 323 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 324 History history = hgRepo.getHistory(root, null, null); 325 assertNotNull(history); 326 assertNotNull(history.getHistoryEntries()); 327 assertEquals(10, history.getHistoryEntries().size()); 328 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 329 collect(Collectors.toList()); 330 assertEquals(List.of(REVISIONS), revisions); 331 } 332 333 @Test testGetHistorySinceTillNullRev()334 void testGetHistorySinceTillNullRev() throws Exception { 335 File root = new File(repository.getSourceRoot(), "mercurial"); 336 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 337 History history = hgRepo.getHistory(root, null, REVISIONS[4]); 338 assertNotNull(history); 339 assertNotNull(history.getHistoryEntries()); 340 assertEquals(6, history.getHistoryEntries().size()); 341 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 342 collect(Collectors.toList()); 343 assertEquals(List.of(Arrays.copyOfRange(REVISIONS, 4, REVISIONS.length)), revisions); 344 } 345 346 @Test testGetHistorySinceTillRevNull()347 void testGetHistorySinceTillRevNull() throws Exception { 348 File root = new File(repository.getSourceRoot(), "mercurial"); 349 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 350 History history = hgRepo.getHistory(root, REVISIONS[3], null); 351 assertNotNull(history); 352 assertNotNull(history.getHistoryEntries()); 353 assertEquals(3, history.getHistoryEntries().size()); 354 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 355 collect(Collectors.toList()); 356 assertEquals(List.of(Arrays.copyOfRange(REVISIONS, 0, 3)), revisions); 357 } 358 359 @Test testGetHistorySinceTillRevRev()360 void testGetHistorySinceTillRevRev() throws Exception { 361 File root = new File(repository.getSourceRoot(), "mercurial"); 362 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 363 History history = hgRepo.getHistory(root, REVISIONS[7], REVISIONS[2]); 364 assertNotNull(history); 365 assertNotNull(history.getHistoryEntries()); 366 assertEquals(5, history.getHistoryEntries().size()); 367 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 368 collect(Collectors.toList()); 369 assertEquals(List.of(Arrays.copyOfRange(REVISIONS, 2, 7)), revisions); 370 } 371 372 @Test testGetHistoryRenamedFileTillRev()373 void testGetHistoryRenamedFileTillRev() throws Exception { 374 RuntimeEnvironment.getInstance().setHandleHistoryOfRenamedFiles(true); 375 File root = new File(repository.getSourceRoot(), "mercurial"); 376 File file = new File(root, "novel.txt"); 377 assertTrue(file.exists() && file.isFile()); 378 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 379 History history = hgRepo.getHistory(file, null, "7:db1394c05268"); 380 assertNotNull(history); 381 assertNotNull(history.getHistoryEntries()); 382 assertEquals(5, history.getHistoryEntries().size()); 383 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 384 collect(Collectors.toList()); 385 assertEquals(List.of(Arrays.copyOfRange(REVISIONS, 2, 7)), revisions); 386 } 387 388 @Test testGetLastHistoryEntry()389 void testGetLastHistoryEntry() throws Exception { 390 File root = new File(repository.getSourceRoot(), "mercurial"); 391 File file = new File(root, "novel.txt"); 392 assertTrue(file.exists() && file.isFile()); 393 MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(root); 394 HistoryEntry historyEntry = hgRepo.getLastHistoryEntry(file, true); 395 assertNotNull(historyEntry); 396 assertEquals("8:6a8c423f5624", historyEntry.getRevision()); 397 } 398 } 399