diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/IOUtils.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/IOUtils.java index 1b0e886208e..fd4e4e54640 100644 --- a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/IOUtils.java +++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/IOUtils.java @@ -18,7 +18,7 @@ */ /* - * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, Trond Norbye. * Portions Copyright (c) 2017, 2021, Chris Fraire . */ @@ -44,6 +44,8 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; + +import org.jetbrains.annotations.NotNull; import org.opengrok.indexer.logger.LoggerFactory; /** @@ -83,21 +85,21 @@ public static void close(Closeable c) { public static void removeRecursive(Path path) throws IOException { Files.walkFileTree(path, new SimpleFileVisitor<>() { @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + public @NotNull FileVisitResult visitFileFailed(Path file, @NotNull IOException exc) throws IOException { // Try to delete the file anyway. Files.delete(file); return FileVisitResult.CONTINUE; } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public @NotNull FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc == null) { Files.delete(dir); return FileVisitResult.CONTINUE; diff --git a/opengrok-web/src/main/java/org/opengrok/web/PageConfig.java b/opengrok-web/src/main/java/org/opengrok/web/PageConfig.java index f6fc1e7fa07..6a7166d00b1 100644 --- a/opengrok-web/src/main/java/org/opengrok/web/PageConfig.java +++ b/opengrok-web/src/main/java/org/opengrok/web/PageConfig.java @@ -1290,7 +1290,7 @@ private File checkFileResolve(File dir, String name, boolean compressed) { * {@code ".gz"} to the filename. If that fails or an uncompressed version of the * file is younger than its compressed version, the uncompressed file gets used. * - * @param filenames filenames to lookup. + * @param filenames filenames to lookup, relative to source root. * @return an empty array if the related directory does not exist or the * given list is {@code null} or empty, otherwise an array, which may * contain {@code null} entries (when the related file could not be found) diff --git a/opengrok-web/src/test/java/org/opengrok/web/PageConfigTest.java b/opengrok-web/src/test/java/org/opengrok/web/PageConfigTest.java index a2b44b407f8..3f7b71f9967 100644 --- a/opengrok-web/src/test/java/org/opengrok/web/PageConfigTest.java +++ b/opengrok-web/src/test/java/org/opengrok/web/PageConfigTest.java @@ -64,11 +64,13 @@ import org.opengrok.indexer.history.RepositoryFactory; import org.opengrok.indexer.index.Indexer; import org.opengrok.indexer.search.QueryBuilder; +import org.opengrok.indexer.util.IOUtils; import org.opengrok.indexer.util.TestRepository; import org.opengrok.indexer.web.DummyHttpServletRequest; import org.opengrok.indexer.web.QueryParameters; import org.opengrok.indexer.web.SortOrder; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -367,6 +369,10 @@ void testGetLatestRevisionViaIndex() throws Exception { String rev = LatestRevisionUtil.getLastRevFromIndex(new File(repository.getSourceRoot(), filePath)); assertNotNull(rev); assertEquals(HASH_AA35C258, rev); + + // cleanup + env.releaseIndexSearchers(); + IOUtils.removeRecursive(env.getDataRootFile().toPath()); } @Test @@ -683,7 +689,7 @@ public String getPathInfo() { @ParameterizedTest @ValueSource(booleans = {true, false}) - void testIsNotModifiedEtag(boolean createTimestamp) throws IOException { + void testIsNotModifiedEtag(boolean createTimestamp) throws Exception { HttpServletRequest req = new DummyHttpServletRequest() { @Override public String getHeader(String name) { @@ -699,8 +705,25 @@ public String getPathInfo() { } }; - // The ETag value depends on the timestamp file. + // Need data root to be populated for the isNotModified() check, so index first. RuntimeEnvironment env = RuntimeEnvironment.getInstance(); + env.setSourceRoot(repository.getSourceRoot()); + env.setDataRoot(repository.getDataRoot()); + env.setProjectsEnabled(true); + env.setHistoryEnabled(true); + RepositoryFactory.initializeIgnoredNames(env); + + Indexer indexer = Indexer.getInstance(); + indexer.prepareIndexer( + env, + true, // search for repositories + true, // scan and add projects + // don't create dictionary + null, // subFiles - needed when refreshing history partially + null); // repositories - needed when refreshing history partially + indexer.doIndexerExecution(null, null); + + // The ETag value depends on the timestamp file. env.refreshDateForLastIndexRun(); Path timestampPath = Path.of(env.getDataRootPath(), IndexTimestamp.TIMESTAMP_FILE_NAME); Files.deleteIfExists(timestampPath); @@ -713,6 +736,10 @@ public String getPathInfo() { HttpServletResponse resp = mock(HttpServletResponse.class); assertFalse(cfg.isNotModified(req, resp)); verify(resp).setHeader(eq(HttpHeaders.ETAG), startsWith("W/")); + + // cleanup + env.releaseIndexSearchers(); + IOUtils.removeRecursive(env.getDataRootFile().toPath()); } @Test @@ -924,4 +951,85 @@ public String getContextPath() { cfg.addScript(scriptName); assertFalse(cfg.getScripts().getScriptNames().contains(scriptName)); } + + @Test + void testFindDataFilesNullOrEmpty() { + HttpServletRequest req = new DummyHttpServletRequest() { + @Override + public String getPathInfo() { + return "path"; + } + }; + + PageConfig cfg = PageConfig.get(req); + assertAll(() -> { + File[] files = cfg.findDataFiles(null); + assertNotNull(files); + assertEquals(0, files.length); + }, + () -> { + File[] files = cfg.findDataFiles(new ArrayList<>()); + assertNotNull(files); + assertEquals(0, files.length); + }); + } + + @Test + void testFindDataFilesNonExistent() { + HttpServletRequest req = new DummyHttpServletRequest() { + @Override + public String getPathInfo() { + return "path"; + } + }; + + PageConfig cfg = PageConfig.get(req); + var filenames = List.of("nonexistent1", "nonexistent2"); + File[] files = cfg.findDataFiles(filenames); + assertNotNull(files); + assertEquals(2, files.length); + assertTrue(Arrays.stream(files).allMatch(Objects::isNull)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testFindDataFiles(boolean isCompressed) throws Exception { + HttpServletRequest req = new DummyHttpServletRequest() { + @Override + public String getPathInfo() { + return "mercurial"; + } + }; + + RuntimeEnvironment env = RuntimeEnvironment.getInstance(); + env.setSourceRoot(repository.getSourceRoot()); + env.setDataRoot(repository.getDataRoot()); + env.setProjectsEnabled(true); + env.setHistoryEnabled(true); + env.setCompressXref(isCompressed); + RepositoryFactory.initializeIgnoredNames(env); + + Indexer indexer = Indexer.getInstance(); + indexer.prepareIndexer( + env, + true, // search for repositories + true, // scan and add projects + // don't create dictionary + null, // subFiles - needed when refreshing history partially + null); // repositories - needed when refreshing history partially + indexer.doIndexerExecution(null, null); + + PageConfig cfg = PageConfig.get(req); + var filenames = List.of("main.c", "nonexistent", "Makefile"); + File[] files = cfg.findDataFiles(filenames); + assertNotNull(files); + assertEquals(filenames.size(), files.length); + assertTrue(files[0].exists()); + assertNull(files[1]); + assertTrue(files[2].exists()); + + // cleanup + env.releaseIndexSearchers(); + IOUtils.removeRecursive(env.getDataRootFile().toPath()); + } }