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) 2008, 2022, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2019, 2020, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.history; 25 26 import static org.junit.jupiter.api.Assertions.assertEquals; 27 import static org.junit.jupiter.api.Assertions.assertFalse; 28 import static org.junit.jupiter.api.Assertions.assertNotEquals; 29 import static org.junit.jupiter.api.Assertions.assertNotNull; 30 import static org.junit.jupiter.api.Assertions.assertNull; 31 import static org.junit.jupiter.api.Assertions.assertThrows; 32 import static org.junit.jupiter.api.Assertions.assertTrue; 33 import static org.opengrok.indexer.condition.RepositoryInstalled.Type.CVS; 34 import static org.opengrok.indexer.condition.RepositoryInstalled.Type.MERCURIAL; 35 import static org.opengrok.indexer.condition.RepositoryInstalled.Type.SUBVERSION; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.nio.file.Files; 41 import java.nio.file.Path; 42 import java.nio.file.Paths; 43 import java.util.ArrayList; 44 import java.util.Collection; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.stream.Collectors; 48 49 import org.junit.jupiter.api.AfterAll; 50 import org.junit.jupiter.api.AfterEach; 51 import org.junit.jupiter.api.BeforeAll; 52 import org.junit.jupiter.api.Test; 53 import org.junit.jupiter.params.ParameterizedTest; 54 import org.junit.jupiter.params.provider.ValueSource; 55 import org.opengrok.indexer.condition.EnabledForRepository; 56 import org.opengrok.indexer.configuration.RuntimeEnvironment; 57 import org.opengrok.indexer.util.FileUtilities; 58 import org.opengrok.indexer.util.TestRepository; 59 60 /** 61 * Test the functionality provided by the HistoryGuru (with friends). 62 * @author Trond Norbye 63 * @author Vladimir Kotal 64 */ 65 public class HistoryGuruTest { 66 67 private static TestRepository repository = new TestRepository(); 68 private static final List<File> FILES = new ArrayList<>(); 69 private static RuntimeEnvironment env; 70 71 private static int savedNestingMaximum; 72 73 @BeforeAll setUpClass()74 public static void setUpClass() throws Exception { 75 env = RuntimeEnvironment.getInstance(); 76 savedNestingMaximum = env.getNestingMaximum(); 77 78 repository = new TestRepository(); 79 repository.create(HistoryGuru.class.getResource("/repositories")); 80 RepositoryFactory.initializeIgnoredNames(env); 81 FileUtilities.getAllFiles(new File(repository.getSourceRoot()), FILES, true); 82 assertNotEquals(0, FILES.size()); 83 84 HistoryGuru histGuru = HistoryGuru.getInstance(); 85 assertNotNull(histGuru); 86 assertEquals(0, histGuru.getRepositories().size()); 87 88 // Add initial set of repositories to HistoryGuru and RuntimeEnvironment. 89 // This is a test in itself. While this makes the structure of the tests 90 // a bit incomprehensible, it does not make sense to run the rest of tests 91 // if the basic functionality does not work. 92 env.setRepositories(repository.getSourceRoot()); 93 assertTrue(histGuru.getRepositories().size() > 0); 94 assertEquals(histGuru.getRepositories().size(), 95 env.getRepositories().size()); 96 97 // Create cache with initial set of repositories. 98 histGuru.createCache(); 99 } 100 101 @AfterAll tearDownClass()102 public static void tearDownClass() { 103 repository.destroy(); 104 } 105 106 @AfterEach tearDown()107 public void tearDown() { 108 env.setNestingMaximum(savedNestingMaximum); 109 } 110 111 @Test testGetRevision()112 void testGetRevision() throws HistoryException, IOException { 113 HistoryGuru instance = HistoryGuru.getInstance(); 114 115 for (File f : FILES) { 116 if (f.isFile() && instance.hasHistory(f)) { 117 for (HistoryEntry entry : instance.getHistory(f).getHistoryEntries()) { 118 String revision = entry.getRevision(); 119 try (InputStream in = instance.getRevision(f.getParent(), f.getName(), revision)) { 120 assertNotNull(in, "Failed to get revision " + revision 121 + " of " + f.getAbsolutePath()); 122 } 123 } 124 } 125 } 126 } 127 128 @Test 129 @EnabledForRepository(SUBVERSION) testBug16465()130 void testBug16465() throws HistoryException, IOException { 131 HistoryGuru instance = HistoryGuru.getInstance(); 132 for (File f : FILES) { 133 if (f.getName().equals("bugreport16465@")) { 134 assertNotNull(instance.getHistory(f), f.getPath() + " must have history"); 135 assertNotNull(instance.annotate(f, null), f.getPath() + " must have annotations"); 136 } 137 } 138 } 139 140 @Test annotation()141 void annotation() throws Exception { 142 HistoryGuru instance = HistoryGuru.getInstance(); 143 for (File f : FILES) { 144 if (instance.hasAnnotation(f)) { 145 instance.annotate(f, null); 146 } 147 } 148 } 149 150 @Test getCacheInfo()151 void getCacheInfo() throws HistoryException { 152 // FileHistoryCache is used by default 153 assertEquals("FileHistoryCache", 154 HistoryGuru.getInstance().getCacheInfo()); 155 } 156 157 @Test testAddRemoveRepositories()158 void testAddRemoveRepositories() { 159 HistoryGuru instance = HistoryGuru.getInstance(); 160 final int numReposOrig = instance.getRepositories().size(); 161 162 // Try to add non-existent repository. 163 Collection<String> repos = new ArrayList<>(); 164 repos.add("totally-nonexistent-repository"); 165 Collection<RepositoryInfo> added = instance.addRepositories(repos); 166 assertEquals(0, added.size()); 167 assertEquals(numReposOrig, instance.getRepositories().size()); 168 169 // Remove one repository. 170 repos = new ArrayList<>(); 171 repos.add(env.getSourceRootPath() + File.separator + "git"); 172 instance.removeRepositories(repos); 173 assertEquals(numReposOrig - 1, instance.getRepositories().size()); 174 175 // Add the repository back. 176 added = instance.addRepositories(repos); 177 assertEquals(1, added.size()); 178 assertEquals(numReposOrig, instance.getRepositories().size()); 179 } 180 181 @Test 182 @EnabledForRepository(CVS) testAddSubRepositoryNotNestable()183 void testAddSubRepositoryNotNestable() { 184 HistoryGuru instance = HistoryGuru.getInstance(); 185 186 // Check out CVS underneath a Git repository. 187 File cvsRoot = new File(repository.getSourceRoot(), "cvs_test"); 188 assertTrue(cvsRoot.exists()); 189 assertTrue(cvsRoot.isDirectory()); 190 File gitRoot = new File(repository.getSourceRoot(), "git"); 191 assertTrue(gitRoot.exists()); 192 assertTrue(gitRoot.isDirectory()); 193 CVSRepositoryTest.runCvsCommand(gitRoot, "-d", 194 cvsRoot.toPath().resolve("cvsroot").toFile().getAbsolutePath(), "checkout", "cvsrepo"); 195 196 Collection<RepositoryInfo> addedRepos = instance. 197 addRepositories(Collections.singleton(Paths.get(repository.getSourceRoot(), 198 "git").toString())); 199 assertEquals(1, addedRepos.size()); 200 } 201 202 @Test 203 @EnabledForRepository(MERCURIAL) testAddSubRepository()204 void testAddSubRepository() { 205 HistoryGuru instance = HistoryGuru.getInstance(); 206 207 // Clone a Mercurial repository underneath a Mercurial repository. 208 File hgRoot = new File(repository.getSourceRoot(), "mercurial"); 209 assertTrue(hgRoot.exists()); 210 assertTrue(hgRoot.isDirectory()); 211 MercurialRepositoryTest.runHgCommand(hgRoot, 212 "clone", hgRoot.getAbsolutePath(), "subrepo"); 213 214 Collection<RepositoryInfo> addedRepos = instance. 215 addRepositories(Collections.singleton(Paths.get(repository.getSourceRoot(), 216 "mercurial").toString())); 217 assertEquals(2, addedRepos.size()); 218 } 219 220 @Test testNestingMaximum()221 void testNestingMaximum() throws IOException { 222 // Just fake a nesting of Repo -> Git -> Git. 223 File repoRoot = new File(repository.getSourceRoot(), "repoRoot"); 224 certainlyMkdirs(repoRoot); 225 File repo0 = new File(repoRoot, ".repo"); 226 certainlyMkdirs(repo0); 227 File sub1 = new File(repoRoot, "sub1"); 228 certainlyMkdirs(sub1); 229 File repo1 = new File(sub1, ".git"); 230 certainlyMkdirs(repo1); 231 File sub2 = new File(sub1, "sub2"); 232 certainlyMkdirs(sub2); 233 File repo2 = new File(sub2, ".git"); 234 certainlyMkdirs(repo2); 235 236 HistoryGuru instance = HistoryGuru.getInstance(); 237 Collection<RepositoryInfo> addedRepos = instance.addRepositories( 238 Collections.singleton(Paths.get(repository.getSourceRoot(), 239 "repoRoot").toString())); 240 assertEquals(2, addedRepos.size(), "should add to default nesting maximum"); 241 242 env.setNestingMaximum(2); 243 addedRepos = instance.addRepositories( 244 Collections.singleton(Paths.get(repository.getSourceRoot(), 245 "repoRoot").toString())); 246 assertEquals(3, addedRepos.size(), "should get one more repo"); 247 } 248 certainlyMkdirs(File file)249 private static void certainlyMkdirs(File file) throws IOException { 250 if (!file.mkdirs()) { 251 throw new IOException("Couldn't mkdirs " + file); 252 } 253 } 254 255 @Test testScanningDepth()256 void testScanningDepth() throws IOException { 257 String topLevelDirName = "scanDepthTest"; 258 File repoRoot = new File(repository.getSourceRoot(), topLevelDirName); 259 certainlyMkdirs(repoRoot); 260 File repo0 = new File(repoRoot, ".git"); 261 certainlyMkdirs(repo0); 262 File sub1 = new File(repoRoot, "sub1"); 263 certainlyMkdirs(sub1); 264 File sub2 = new File(sub1, "sub2"); 265 certainlyMkdirs(sub2); 266 File sub3 = new File(sub2, ".git"); 267 certainlyMkdirs(sub3); 268 269 int originalScanDepth = env.getScanningDepth(); 270 env.setScanningDepth(0); 271 272 HistoryGuru instance = HistoryGuru.getInstance(); 273 Collection<RepositoryInfo> addedRepos = instance.addRepositories( 274 Collections.singleton(Paths.get(repository.getSourceRoot(), topLevelDirName).toString())); 275 assertEquals(1, addedRepos.size(), "should add to max depth"); 276 277 env.setScanningDepth(1); 278 List<String> repoDirs = addedRepos.stream().map(RepositoryInfo::getDirectoryName).collect(Collectors.toList()); 279 instance.removeRepositories(repoDirs); 280 addedRepos = instance.addRepositories( 281 Collections.singleton(Paths.get(repository.getSourceRoot(), topLevelDirName).toString())); 282 assertEquals(2, addedRepos.size(), "should add to increased max depth"); 283 284 env.setScanningDepth(originalScanDepth); 285 } 286 287 @Test testGetLastHistoryEntryNonexistent()288 void testGetLastHistoryEntryNonexistent() throws HistoryException { 289 HistoryGuru instance = HistoryGuru.getInstance(); 290 File file = new File("/nonexistent"); 291 assertThrows(HistoryException.class, () -> instance.getLastHistoryEntry(file, true)); 292 } 293 294 @ParameterizedTest 295 @ValueSource(booleans = {true, false}) testGetLastHistoryEntryVsIndexer(boolean isIndexerParam)296 void testGetLastHistoryEntryVsIndexer(boolean isIndexerParam) throws HistoryException { 297 boolean isIndexer = env.isIndexer(); 298 env.setIndexer(isIndexerParam); 299 boolean isTagsEnabled = env.isTagsEnabled(); 300 env.setTagsEnabled(true); 301 HistoryGuru instance = HistoryGuru.getInstance(); 302 File file = new File(repository.getSourceRoot(), "git"); 303 assertTrue(file.exists()); 304 if (isIndexerParam) { 305 assertThrows(IllegalStateException.class, () -> instance.getLastHistoryEntry(file, true)); 306 } else { 307 assertNotNull(instance.getLastHistoryEntry(file, true)); 308 } 309 env.setIndexer(isIndexer); 310 env.setTagsEnabled(isTagsEnabled); 311 } 312 313 @Test testGetLastHistoryEntryRepoHistoryDisabled()314 void testGetLastHistoryEntryRepoHistoryDisabled() throws Exception { 315 File file = new File(repository.getSourceRoot(), "git"); 316 assertTrue(file.exists()); 317 HistoryGuru instance = HistoryGuru.getInstance(); 318 Repository repository = instance.getRepository(file); 319 320 // HistoryGuru is final class so cannot be reasonably mocked with Mockito. 321 // In order to avoid getting the history from the cache, move the cache away for a bit. 322 String dirName = FileHistoryCache.getRepositoryHistDataDirname(repository); 323 assertNotNull(dirName); 324 Path histPath = Path.of(dirName); 325 Path tmpHistPath = Path.of(dirName + ".disabled"); 326 Files.move(histPath, tmpHistPath); 327 assertFalse(histPath.toFile().exists()); 328 329 assertNotNull(repository); 330 repository.setHistoryEnabled(false); 331 assertNull(instance.getLastHistoryEntry(file, false)); 332 333 // cleanup 334 repository.setHistoryEnabled(true); 335 Files.move(tmpHistPath, histPath); 336 } 337 } 338