/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.core.util.lineconsumer;
import org.eclipse.che.commons.test.mockito.answer.WaitingAnswer;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.slf4j.LoggerFactory.getLogger;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.fail;
/**
* @author Mykola Morhun
*/
@Listeners(value = {MockitoTestNGListener.class})
public class ConcurrentFileLineConsumerTest {
private static final Logger LOG = getLogger(ConcurrentFileLineConsumer.class);
@Mock
private Writer writer;
private ConcurrentFileLineConsumer concurrentFileLineConsumer;
private ExecutorService executor;
private File file;
@BeforeMethod
public void beforeMethod() throws Exception {
file = File.createTempFile("file", ".tmp");
concurrentFileLineConsumer = new ConcurrentFileLineConsumer(file);
injectWriterMock(concurrentFileLineConsumer, writer);
executor = Executors.newFixedThreadPool(3);
}
@AfterMethod
public void afterMethod() {
executor.shutdownNow();
}
@AfterClass
public void afterClass() {
if (!file.delete()) {
LOG.warn("Failed to remove temporary file: '{}'.", file);
}
}
@Test
public void shouldBeAbleToWriteIntoFile() throws Exception {
// given
final String message = "Test line";
// when
concurrentFileLineConsumer.writeLine(message);
// then
verify(writer).write(eq(message));
}
@Test
public void shouldNotWriteIntoFileAfterConsumerClosing() throws Exception {
// given
final String message = "Test line";
// when
concurrentFileLineConsumer.close();
concurrentFileLineConsumer.writeLine(message);
// then
verify(writer, never()).write(anyString());
}
@Test
public void shouldBeAbleToWriteIntoFileSimultaneously() throws Exception {
// given
final String message1 = "Message 1";
final String message2 = "Message 2";
WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>();
doAnswer(waitingAnswer).when(writer).write(eq(message1));
executor.execute(() -> writeIntoConsumer(message1));
waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
// when
writeIntoConsumer(message2);
waitingAnswer.completeAnswer();
// then
awaitFinalization();
verify(writer).write(eq(message1));
verify(writer).write(eq(message2));
}
@Test
public void closeOperationShouldWaitUntilAllCurrentWriteOperationsWillBeFinished() throws Exception {
// given
final String message1 = "Message 1";
final String message2 = "Message 2";
WaitingAnswer<Void> waitingAnswer1 = waitOnWrite(message1);
WaitingAnswer<Void> waitingAnswer2 = waitOnWrite(message2);
// when
executor.execute(this::closeConsumer);
waitingAnswer1.completeAnswer();
waitingAnswer2.completeAnswer();
// then
awaitFinalization();
assertFalse(concurrentFileLineConsumer.isOpen());
verify(writer).write(eq(message1));
verify(writer).write(eq(message2));
}
@Test
public void shouldNotWriteIntoSubConsumersWhenLockForCloseIsLocked() throws Exception {
// given
WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>();
doAnswer(waitingAnswer).when(writer).close();
executor.execute(this::closeConsumer);
waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
// when
writeIntoConsumer("Test line");
waitingAnswer.completeAnswer();
// then
awaitFinalization();
verify(writer, never()).write(anyString());
}
/**
* Inject Writer mock into FileLineConsumer class.
* This allow to test the FileLineConsumer operations.
*
* @param concurrentFileLineConsumer
* instance in which mock will be injected
* @param writerMock
* mock to inject
* @throws Exception
*/
private void injectWriterMock(ConcurrentFileLineConsumer concurrentFileLineConsumer, Writer writerMock) throws Exception {
Field writerField = concurrentFileLineConsumer.getClass().getDeclaredField("writer");
writerField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(writerField, writerField.getModifiers() & ~Modifier.FINAL);
writerField.set(concurrentFileLineConsumer, writerMock);
}
private void writeIntoConsumer(String message) {
try {
concurrentFileLineConsumer.writeLine(message);
} catch (IOException ignore) {
}
}
private void closeConsumer() {
try {
concurrentFileLineConsumer.close();
} catch (IOException ignore) {
}
}
/**
* Executes write line into file in separate thread and waits on writing to file operation until this thread released.
*
* @param message
* message to write
* @return waiting answer to release this thread later using {@link WaitingAnswer#completeAnswer()}
* @throws Exception
*/
private WaitingAnswer<Void> waitOnWrite(String message) throws Exception {
WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>();
doAnswer(waitingAnswer).when(writer).write(eq(message));
executor.execute(() -> writeIntoConsumer(message));
waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
return waitingAnswer;
}
private void awaitFinalization() throws Exception {
executor.shutdown();
if (!executor.awaitTermination(1_000, TimeUnit.MILLISECONDS)) {
fail("Operation is hanged up. Terminated.");
}
}
}