diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java index 8d24bed9436..2f04697971d 100644 --- a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java +++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java @@ -711,7 +711,7 @@ public synchronized boolean validateUniversalCtags() { ctagsLanguages.addAll(languages); } - ctagsFound = CtagsUtil.validate(ctagsBinary); + ctagsFound = CtagsUtil.isValid(ctagsBinary); } if (ctagsFound) { diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/CtagsUtil.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/CtagsUtil.java index bdea93b4354..e8b8f42bed2 100644 --- a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/CtagsUtil.java +++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/CtagsUtil.java @@ -25,6 +25,7 @@ import org.apache.commons.lang3.SystemUtils; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; import org.opengrok.indexer.analysis.Ctags; import org.opengrok.indexer.analysis.Definitions; import org.opengrok.indexer.configuration.RuntimeEnvironment; @@ -60,29 +61,33 @@ private CtagsUtil() { /** * Check that {@code ctags} program exists and is working. - * @param ctagsBinary name of the ctags program or path + * @param ctagsBinary name of the {@code ctags} program or path * @return true if the program works, false otherwise */ - public static boolean validate(String ctagsBinary) { + public static boolean isValid(String ctagsBinary) { if (!isUniversalCtags(ctagsBinary)) { return false; } - return canProcessFiles(RuntimeEnvironment.getInstance().getSourceRootFile()); + // The source root can be read-only. In such case, fall back to the default + // temporary directory as a second-best choice how to test that ctags is working. + return (canProcessFiles(RuntimeEnvironment.getInstance().getSourceRootFile()) || + canProcessFiles(new File(System.getProperty("java.io.tmpdir")))); } /** - * Run ctags program on a known temporary file to be created under given path and see if it was possible - * to get some symbols. + * Run {@code ctags} program on a known temporary file to be created under given path + * and see if it was possible to get some symbols. * @param baseDir directory to use for storing the temporary file * @return true if at least one symbol was found, false otherwise */ - private static boolean canProcessFiles(File baseDir) { + @VisibleForTesting + static boolean canProcessFiles(File baseDir) { Path inputPath; try { inputPath = File.createTempFile("ctagsValidation", ".c", baseDir).toPath(); } catch (IOException e) { - LOGGER.log(Level.WARNING, "cannot create temporary file in ''{0}''", baseDir); + LOGGER.log(Level.WARNING, String.format("cannot create temporary file in '%s'", baseDir), e); return false; } final String resourceFileName = "sample.c"; diff --git a/opengrok-indexer/src/test/java/org/opengrok/indexer/util/CtagsUtilTest.java b/opengrok-indexer/src/test/java/org/opengrok/indexer/util/CtagsUtilTest.java index f2442d2d1e9..5bef12d45d5 100644 --- a/opengrok-indexer/src/test/java/org/opengrok/indexer/util/CtagsUtilTest.java +++ b/opengrok-indexer/src/test/java/org/opengrok/indexer/util/CtagsUtilTest.java @@ -18,12 +18,14 @@ */ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Chris Fraire . */ package org.opengrok.indexer.util; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.opengrok.indexer.configuration.RuntimeEnvironment; import java.io.IOException; @@ -35,6 +37,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; /** * Represents a container for tests of {@link CtagsUtil}. @@ -51,19 +56,40 @@ void getLanguages() { } @Test - void validate() throws IOException { + void testIsValid() throws IOException { RuntimeEnvironment env = RuntimeEnvironment.getInstance(); Path tmpSourceRoot = Files.createTempDirectory("srcRootCtagsValidationTest"); env.setSourceRoot(tmpSourceRoot.toString()); assertTrue(env.getSourceRootFile().exists()); - assertTrue(CtagsUtil.validate(env.getCtags())); + assertTrue(CtagsUtil.isValid(env.getCtags())); Files.delete(tmpSourceRoot); } + /** + * Simulate non-writable source root and verify that {@link CtagsUtil#isValid(String)} still returns true + * as it should fall back to default temporary directory. + */ @Test - void testValidateWithInvalidExtraOptions() throws IOException { + void testIsValidNoWritableSourceRoot() throws IOException { + RuntimeEnvironment env = RuntimeEnvironment.getInstance(); + Path tmpSourceRoot = Files.createTempDirectory("negativeCtagsValidationTest"); + env.setSourceRoot(tmpSourceRoot.toString()); + assertTrue(env.getSourceRootFile().exists()); + + try (MockedStatic mocked = mockStatic(CtagsUtil.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(() -> CtagsUtil.canProcessFiles(env.getSourceRootFile())).thenReturn(false); + assertTrue(CtagsUtil.isValid(env.getCtags())); + mocked.verify(() -> CtagsUtil.canProcessFiles(eq(env.getSourceRootFile())), + times(2)); // one extra for the lambda call above + } + + Files.delete(tmpSourceRoot); + } + + @Test + void testIsValidWithInvalidExtraOptions() throws IOException { RuntimeEnvironment env = RuntimeEnvironment.getInstance(); Path tmpSourceRoot = Files.createTempDirectory("srcRootCtagsValidationTestExtraArgs"); env.setSourceRoot(tmpSourceRoot.toString()); @@ -74,7 +100,7 @@ void testValidateWithInvalidExtraOptions() throws IOException { String extraOptionsAbsPath = extraOptionsPath.toAbsolutePath().toString(); env.setCTagsExtraOptionsFile(extraOptionsAbsPath); - assertFalse(CtagsUtil.validate(env.getCtags())); + assertFalse(CtagsUtil.isValid(env.getCtags())); // cleanup env.setCTagsExtraOptionsFile(null); diff --git a/pom.xml b/pom.xml index 4dcbbb36b8d..ca74fbfb047 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ Portions Copyright (c) 2018, 2020, Chris Fraire . 3.0.0-M5 3.13.0 1.11.4 - 3.12.4 + 5.2.0 2.14.0