/*
|
* The contents of this file are subject to the terms of the Common Development and
|
* Distribution License (the License). You may not use this file except in compliance with the
|
* License.
|
*
|
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
* specific language governing permission and limitations under the License.
|
*
|
* When distributing Covered Software, include this CDDL Header Notice in each file and include
|
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
* Header, with the fields enclosed by brackets [] replaced by your own identifying
|
* information: "Portions copyright [year] [name of copyright owner]".
|
*
|
* Copyright 2026 3A Systems, LLC.
|
*/
|
package org.opends.quicksetup;
|
|
import static java.nio.charset.Charset.defaultCharset;
|
import static java.nio.file.StandardOpenOption.APPEND;
|
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertFalse;
|
import static org.testng.Assert.assertNotEquals;
|
import static org.testng.Assert.assertTrue;
|
import static org.testng.Assert.fail;
|
|
import java.io.File;
|
import java.io.IOException;
|
import java.lang.reflect.Method;
|
import java.nio.file.Files;
|
import java.util.ArrayList;
|
import java.util.Collection;
|
import java.util.List;
|
|
import org.opends.server.DirectoryServerTestCase;
|
import org.opends.server.TestCaseUtils;
|
import org.opends.server.loggers.ErrorLogger;
|
import org.testng.SkipException;
|
import org.testng.annotations.AfterClass;
|
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.Test;
|
|
/**
|
* Tests that a {@link TempLogFile} can be placed in a directory of the caller's choosing (the
|
* instance {@code logs/} directory for setup, so that {@code start-ds} does not sweep it away
|
* with the rest of {@code tmp/}, see issue #1030), and that it tells whether the file is still
|
* there to be read.
|
*/
|
@SuppressWarnings("javadoc")
|
@Test(groups = { "precommit", "quicksetup" }, sequential = true)
|
public class TempLogFileTest extends DirectoryServerTestCase
|
{
|
private static final String PREFIX = "opendj-setup-";
|
|
private File tempDir;
|
private final List<TempLogFile> created = new ArrayList<>();
|
|
@BeforeClass
|
public void setUp() throws IOException
|
{
|
tempDir = TestCaseUtils.createTemporaryDirectory("tempLogFileTest");
|
}
|
|
@AfterClass
|
public void tearDown() throws IOException
|
{
|
for (TempLogFile logFile : created)
|
{
|
logFile.deleteLogFileAfterSuccess();
|
}
|
TestCaseUtils.deleteDirectory(tempDir);
|
}
|
|
private TempLogFile track(TempLogFile logFile)
|
{
|
created.add(logFile);
|
return logFile;
|
}
|
|
private static File parentOf(TempLogFile logFile) throws IOException
|
{
|
return logFile.getLogFile().getCanonicalFile().getParentFile();
|
}
|
|
/** The directory does not exist before setup lays the instance down: it has to be created. */
|
@Test
|
public void testLogFileIsCreatedInTheRequestedDirectory() throws Exception
|
{
|
final File logs = new File(tempDir, "not-yet-laid-down/logs");
|
assertFalse(logs.exists());
|
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, logs));
|
|
assertTrue(logFile.isEnabled());
|
assertTrue(logFile.isReadable());
|
assertEquals(parentOf(logFile), logs.getCanonicalFile());
|
assertTrue(logFile.getLogFile().getName().startsWith(PREFIX), logFile.getPath());
|
assertTrue(logFile.getLogFile().getName().endsWith(".log"), logFile.getPath());
|
}
|
|
@Test
|
public void testReadContentsReturnsWhatIsInTheFile() throws Exception
|
{
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, new File(tempDir, "logs")));
|
// The log's own stream is not in append mode and sits at the end of its own bytes, so a
|
// record written after the marker would be written over it: shut the writer first, and
|
// the marker is the last thing in the file whatever else the JVM logs.
|
logFile.writer.shutdown();
|
final String marker = "the last line written before the failure";
|
Files.write(logFile.getLogFile().toPath(), (marker + "\n").getBytes(defaultCharset()), APPEND);
|
|
assertTrue(logFile.readContents().endsWith(marker + "\n"));
|
}
|
|
/** Being enabled means messages are logged; being readable means the file is there to hand over. */
|
@Test
|
public void testIsReadableFollowsTheFileNotTheLogger() throws Exception
|
{
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, new File(tempDir, "logs")));
|
assertTrue(logFile.isReadable());
|
|
// Not File.delete(): the writer still holds the file, and Windows does not delete a file
|
// that is open. deleteLogFileAfterSuccess() shuts the writer first, as setup does.
|
logFile.deleteLogFileAfterSuccess();
|
assertFalse(logFile.getLogFile().exists());
|
|
assertTrue(logFile.isEnabled());
|
assertFalse(logFile.isReadable());
|
try
|
{
|
logFile.readContents();
|
fail("reading a deleted log must fail");
|
}
|
catch (IOException expected)
|
{
|
// the caller reports it instead of promising the file
|
}
|
}
|
|
/** A directory where the log was is not a log: there is nothing to hand over either. */
|
@Test
|
public void testADirectoryAtTheLogPathIsNotReadable() throws Exception
|
{
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, new File(tempDir, "logs")));
|
logFile.deleteLogFileAfterSuccess();
|
assertTrue(logFile.getLogFile().mkdir());
|
|
assertTrue(Files.isReadable(logFile.getLogFile().toPath()));
|
assertFalse(logFile.isReadable());
|
}
|
|
@Test
|
public void testNoDirectoryMeansTheTemporaryDirectory() throws Exception
|
{
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, null));
|
|
assertTrue(logFile.isEnabled());
|
assertEquals(parentOf(logFile), new File(System.getProperty("java.io.tmpdir")).getCanonicalFile());
|
}
|
|
/** A directory that cannot be used must not cost the log: fall back to the temporary directory. */
|
@Test
|
public void testUnusableDirectoryFallsBackToTheTemporaryDirectory() throws Exception
|
{
|
final File notADirectory = new File(tempDir, "not-a-directory");
|
assertTrue(notADirectory.createNewFile());
|
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, notADirectory));
|
|
assertTrue(logFile.isEnabled());
|
assertTrue(logFile.isReadable());
|
assertNotEquals(parentOf(logFile), notADirectory.getCanonicalFile());
|
assertEquals(parentOf(logFile), new File(System.getProperty("java.io.tmpdir")).getCanonicalFile());
|
|
// Why the log is not where it was asked for is warned about once there is a log to carry
|
// the warning: at the point the directory failed, no publisher was installed yet.
|
assumeTheLogGoesToTheFile();
|
logFile.writer.shutdown();
|
final String contents = logFile.readContents();
|
assertTrue(contents.contains("falling back to the temporary directory"), contents);
|
assertTrue(contents.contains(notADirectory.toString()), contents);
|
}
|
|
/**
|
* The publishers the constructor puts on the logger singletons come off again with the log:
|
* the singletons outlive the file, and what a leaked publisher is handed goes to a closed
|
* stream and is swallowed.
|
*/
|
@Test
|
public void testDeletingTheLogTakesItsPublisherOffTheLogger() throws Exception
|
{
|
final int before = errorLogPublishers();
|
|
final TempLogFile logFile = track(TempLogFile.newTempLogFile(PREFIX, new File(tempDir, "logs")));
|
assertEquals(errorLogPublishers(), before + 1, "the log logs through a publisher of its own");
|
|
logFile.deleteLogFileAfterSuccess();
|
|
assertEquals(errorLogPublishers(), before, "the publisher must not outlive the log");
|
}
|
|
/** How many publishers the error logger holds; {@code getLogPublishers()} is protected. */
|
private static int errorLogPublishers() throws Exception
|
{
|
final Method getLogPublishers = ErrorLogger.class.getDeclaredMethod("getLogPublishers");
|
getLogPublishers.setAccessible(true);
|
return ((Collection<?>) getLogPublishers.invoke(ErrorLogger.getInstance())).size();
|
}
|
|
/**
|
* {@code OPENDJ_LOG_TO_STDOUT} sends the records to stdout and leaves the file empty, so a
|
* case which reads the log back has nothing to look at.
|
*/
|
private static void assumeTheLogGoesToTheFile()
|
{
|
if ("true".equalsIgnoreCase(System.getenv("OPENDJ_LOG_TO_STDOUT")))
|
{
|
throw new SkipException("OPENDJ_LOG_TO_STDOUT writes the log to stdout, not to the file");
|
}
|
}
|
}
|