/**
* Copyright 2013 the original author or authors.
* <p>
* Licensed 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 io.neba.core.logviewer;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
/**
* @author Olaf Otto
*/
@RunWith(MockitoJUnitRunner.class)
public abstract class TailTests {
private File testLogfileDirectory;
private StringBuilder receivedText;
private Answer<?> recordText;
@Mock
private RemoteEndpoint remote;
@Before
public final void setUp() throws Exception {
URL testLogfileUrl = getClass().getResource("/io/neba/core/logviewer/testlogfiles/");
this.testLogfileDirectory = new File(testLogfileUrl.getFile());
this.receivedText = new StringBuilder(4096);
this.recordText = invocation -> {
ByteBuffer buffer = (ByteBuffer) invocation.getArguments()[0];
byte[] contents = new byte[buffer.limit()];
buffer.get(contents, 0, contents.length);
receivedText.append(new String(contents, "UTF-8"));
return null;
};
doAnswer(recordText).when(remote).sendBytes(any());
}
public File getTestLogfileDirectory() {
return testLogfileDirectory;
}
public StringBuilder getReceivedText() {
return receivedText;
}
public RemoteEndpoint getRemote() {
return remote;
}
public Object assertSendTextContains(String text) {
return assertThat(normalizeLineBreaks(getReceivedText().toString()))
.contains(normalizeLineBreaks(text));
}
public String pathOf(String relativePath) {
return new File(getTestLogfileDirectory(), relativePath).getAbsolutePath();
}
public void verifyNoTextWasSent() throws IOException {
verify(getRemote(), never()).sendBytes(any());
}
public void write(File file, String line) throws IOException {
try (FileWriter writer = new FileWriter(file)) {
writer.write(line);
}
}
public void sleepUpTo(long amount, TimeUnit unit) {
yield();
try {
sleep(unit.toMillis(amount));
} catch (InterruptedException e) {
// Continue.
}
}
public static String normalizeLineBreaks(String s) {
return s.replaceAll("[\r\n]+", "\n");
}
/**
* Executes the callback as soon as {@link RemoteEndpoint#sendBytes(ByteBuffer) the mocked remote receives bytes}.
* This allows reacting when {@link Tail} is picking up data from a log file.
*
* @param c must not be <code>null</code>.
*/
public void uponWriteToRemoteDo(Callable c) throws IOException {
Thread unitTest = currentThread();
doAnswer(invocation -> {
// Still track the received text
recordText.answer(invocation);
// As soon as bytes are received, call the callable and interrupt the test case, as it
// may await this event.
c.call();
synchronized (unitTest) {
unitTest.interrupt();
}
return null;
}).when(getRemote()).sendBytes(isA(ByteBuffer.class));
}
/**
* Expects the provided assertion not fail within ten seconds, trying every 100 milliseconds.
*
* @param runnable not null. Expected to throw an exception should the embodied exception fail.
*/
public void eventually(Runnable runnable) {
long max = SECONDS.toMillis(10),
waited = 0,
interval = 100;
Throwable issue = null;
while (waited < max) {
try {
runnable.run();
return;
} catch (Throwable t) {
issue = t;
long timeBeforeSleep = currentTimeMillis();
try {
sleep(interval);
} catch (InterruptedException e1) {
// continue
}
waited += (currentTimeMillis() - timeBeforeSleep);
}
}
throw new AssertionError("Unable to satisfy within " + MILLISECONDS.toSeconds(max) + " seconds.", issue);
}
}