/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.process;
import static org.junit.Assert.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.apache.geode.internal.util.StopWatch;
import org.apache.geode.test.junit.categories.IntegrationTest;
import org.apache.geode.test.junit.rules.ExpectedTimeoutRule;
/**
* Unit tests the PidFile class.
*
* @since GemFire 8.2
*/
@Category(IntegrationTest.class)
public class PidFileJUnitTest {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public ExpectedTimeoutRule timeout = ExpectedTimeoutRule.none();
protected Mockery mockContext;
private ExecutorService futures;
@Before
public void before() {
mockContext = new Mockery() {
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};
this.futures = Executors.newFixedThreadPool(2);
}
@After
public void after() {
mockContext.assertIsSatisfied();
assertTrue(this.futures.shutdownNow().isEmpty());
}
@Test
public void readsIntFromFile() throws Exception {
final File file = testFolder.newFile("my.pid");
final String value = "42";
writeToFile(file, value);
final int readValue = new PidFile(file).readPid();
assertEquals(Integer.parseInt(value), readValue);
}
@Test
public void readingEmptyFileThrowsIllegalArgumentException() throws Exception {
final File file = testFolder.newFile("my.pid");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid pid 'null' found");
new PidFile(file).readPid();
}
@Test
public void readingFileWithNonIntegerThrowsIllegalArgumentException() throws Exception {
final File file = testFolder.newFile("my.pid");
final String value = "fortytwo";
writeToFile(file, value);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid pid '" + value + "' found");
new PidFile(file).readPid();
}
@Test
public void readingFileWithNegativeIntegerThrowsIllegalArgumentException() throws Exception {
final File file = testFolder.newFile("my.pid");
final String value = "-42";
writeToFile(file, value);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid pid '" + value + "' found");
new PidFile(file).readPid();
}
@Test
public void readingNullFileThrowsNullPointerException() throws Exception {
final File file = null;
thrown.expect(NullPointerException.class);
new PidFile(file).readPid();
}
@Test
public void timesOutReadingFromEmptyFile() throws Exception {
final File file = testFolder.newFile("my.pid");
timeout.expect(TimeoutException.class);
timeout.expectMessage("Invalid pid 'null' found");
timeout.expectMinimumDuration(1000);
timeout.expectMaximumDuration(10000);
timeout.expectTimeUnit(TimeUnit.MILLISECONDS);
new PidFile(file).readPid(1500, TimeUnit.MILLISECONDS);
}
@Test
public void readsIntBeforeTimeout() throws Exception {
final int AWAIT_LATCH_TIMEOUT_MILLIS = 10 * 1000;
final int OPEN_LATCH_DELAY_MILLIS = 2 * 1000;
final int FUTURE_GET_TIMEOUT_MILLIS = 2 * 1000;
final int READ_PID_TIMEOUT_MILLIS = 2 * OPEN_LATCH_DELAY_MILLIS;
final File file = testFolder.newFile("my.pid");
final FileWriter writer = new FileWriter(file);
final CountDownLatch writePidLatch = new CountDownLatch(1);
final String value = "42";
// start Future to write the pid later but before timeout
Future<Boolean> futureWritePid = this.futures.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
writePidLatch.await(AWAIT_LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
writeToFile(file, value);
return true;
}
});
// start Future to sleep and release the delay
Future<Boolean> futureOpenLatch = this.futures.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
Thread.sleep(OPEN_LATCH_DELAY_MILLIS);
writePidLatch.countDown();
return true;
}
});
StopWatch stopWatch = new StopWatch(true);
final int readValue = new PidFile(file).readPid(READ_PID_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
assertEquals(Integer.parseInt(value), readValue);
long duration = stopWatch.elapsedTimeMillis();
assertTrue(duration > OPEN_LATCH_DELAY_MILLIS);
assertTrue(duration < READ_PID_TIMEOUT_MILLIS);
assertEquals(0, writePidLatch.getCount());
assertTrue(futureOpenLatch.get(FUTURE_GET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
assertTrue(futureWritePid.get(FUTURE_GET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
}
@Test
public void findsCorrectFile() throws Exception {
final File directory = testFolder.getRoot();
final String fileNames[] = new String[] {"other.txt", "my.txt", "a.log", "b.log"};
for (String fileName : fileNames) {
testFolder.newFile(fileName);
}
final int pidValue = 42;
final File file = testFolder.newFile("my.pid");
writeToFile(file, String.valueOf(pidValue));
final File other = testFolder.newFile("other.pid");
writeToFile(other, "43");
final File[] files = directory.listFiles();
assertEquals(fileNames.length + 2, files.length);
PidFile pidFile = new PidFile(directory, file.getName());
assertEquals(file, pidFile.getFile());
int value = pidFile.readPid();
assertEquals(pidValue, value);
}
@Test
public void missingFileInEmptyDirectoryThrowsFileNotFoundException() throws Exception {
final File directory = testFolder.getRoot();
final String pidFileName = "my.pid";
thrown.expect(FileNotFoundException.class);
thrown.expectMessage("Unable to find PID file '" + pidFileName + "' in directory " + directory);
new PidFile(directory, pidFileName);
}
@Test
public void missingFileThrowsFileNotFoundException() throws Exception {
final String pidFileName = "my.pid";
final File directory = testFolder.getRoot();
final File file = new File(directory, pidFileName);
thrown.expect(FileNotFoundException.class);
thrown.expectMessage("Unable to find PID file '" + file + "'");
new PidFile(file);
}
@Test
public void missingFileInFullDirectoryThrowsFileNotFoundException() throws Exception {
final File directory = testFolder.getRoot();
final String fileNames[] = new String[] {"other.txt", "my.txt", "a.log", "b.log"};
for (String fileName : fileNames) {
testFolder.newFile(fileName);
}
final File other = testFolder.newFile("other.pid");
writeToFile(other, "43");
final File[] files = directory.listFiles();
assertEquals(fileNames.length + 1, files.length);
final String pidFileName = "my.pid";
thrown.expect(FileNotFoundException.class);
thrown.expectMessage("Unable to find PID file '" + pidFileName + "' in directory " + directory);
new PidFile(directory, pidFileName);
}
private void writeToFile(final File file, String value) throws IOException {
final FileWriter writer = new FileWriter(file);
writer.write(value);
writer.flush();
writer.close();
}
}