Changed prompt interruption contract

Signed-off-by: Emmanuel Bigeon <emmanuel@bigeon.fr>
This commit is contained in:
Emmanuel Bigeon 2018-12-01 10:54:51 -05:00
parent c4bbfd8434
commit c206b5b22c
5 changed files with 86 additions and 18 deletions

View File

@ -56,6 +56,11 @@
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>net.bigeon.test</groupId>
<artifactId>junitmt</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
<reporting>
<plugins>

View File

@ -8,18 +8,19 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.function.Supplier;
import net.bigeon.gclc.manager.ConsoleInput;
import net.bigeon.test.junitmt.FunctionalTestRunnable;
import net.bigeon.test.junitmt.TestFunction;
import net.bigeon.test.junitmt.ThreadTest;
/**
* @author Emmanuel Bigeon
*
*/
/** @author Emmanuel Bigeon */
public final class InputContract {
public void testInputContract(final Supplier<ConsoleInput> inputs)
throws IOException {
throws IOException, InterruptedException {
// Test close contract
final ConsoleInput input = inputs.get();
assertFalse("An input should not initially be closed", input.isClosed());
@ -33,5 +34,29 @@ public final class InputContract {
} catch (final IOException e) {
// ok
}
// Test interruption contract
final ConsoleInput input2 = inputs.get();
final FunctionalTestRunnable prompting = new FunctionalTestRunnable(
new TestFunction() {
@Override
public void apply() throws Exception {
try {
input2.prompt();
fail("Interrupted prompt should throw INterruptedIOException");
} catch (final InterruptedIOException e) {
// ok
}
}
});
final Thread th = new Thread(prompting);
th.start();
// while (!input2.isPrompting()) {
th.join(200);
//
// }
input2.interruptPrompt();
ThreadTest.assertRuns(th, prompting);
}
}

View File

@ -94,8 +94,7 @@ public interface ConsoleInput extends AutoCloseable {
/** Indicate to the input that is should interrompt the prompting, if possible.
* <p>
* The pending {@link #prompt()} or {@link #prompt(String)} operations should
* return immediatly. However the returned value can be anything (from the
* partial prompt content to an empty string or even a null pointer). */
* return immediately by throwing an InterruptedIOException. */
void interruptPrompt();
/** Test if the input is closed.

View File

@ -111,6 +111,7 @@ public final class ReadingRunnable implements Runnable {
private final Map<String, Object> messageBlocker = new ConcurrentHashMap<>();
/** The lock. */
private final Object messageBlockerLock = new Object();
private boolean interrupting = false;
/** Create a reading runnable.
*
* @param reader the input to read from */
@ -146,8 +147,7 @@ public final class ReadingRunnable implements Runnable {
public String getMessage() throws IOException {
synchronized (lock) {
if (!messages.isEmpty()) {
notifyMessage(messages.peek());
return messages.poll();
return pollMessage();
}
if (!running) {
throw new IOException(CLOSED_PIPE);
@ -156,8 +156,7 @@ public final class ReadingRunnable implements Runnable {
waitMessage(TIMEOUT);
LOGGER.log(Level.FINEST, "Polled: {0}", messages.peek()); //$NON-NLS-1$
waiting = false;
notifyMessage(messages.peek());
return messages.poll();
return pollMessage();
}
}
@ -169,8 +168,7 @@ public final class ReadingRunnable implements Runnable {
public String getNextMessage(final long timeout) throws IOException {
synchronized (lock) {
if (!messages.isEmpty()) {
notifyMessage(messages.peek());
return messages.poll();
return pollMessage();
}
if (!running) {
throw new IOException(CLOSED_PIPE);
@ -181,11 +179,19 @@ public final class ReadingRunnable implements Runnable {
if (messages.isEmpty()) {
return null;
}
notifyMessage(messages.peek());
return messages.poll();
return pollMessage();
}
}
private String pollMessage() throws InterruptedIOException {
final String msg = messages.poll();
if (msg.isEmpty() && interrupting) {
throw new InterruptedIOException();
}
notifyMessage(msg);
return msg;
}
/** Wait for a message to be delivered.
*
* @param message the message
@ -221,7 +227,8 @@ public final class ReadingRunnable implements Runnable {
public void interrupt() {
synchronized (lock) {
if (waiting) {
messages.offer(""); //$NON-NLS-1$
messages.offer("");
interrupting = true;
lock.notifyAll();
}
}

View File

@ -80,6 +80,7 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
@ -90,6 +91,9 @@ import org.junit.Before;
import org.junit.Test;
import net.bigeon.gclc.utils.ReadingRunnable;
import net.bigeon.test.junitmt.FunctionalTestRunnable;
import net.bigeon.test.junitmt.TestFunction;
import net.bigeon.test.junitmt.ThreadTest;
/**
* <p>
@ -198,9 +202,11 @@ public class ReadingRunnableTest {
public final void testGetPendingMessages() throws IOException, InterruptedException {
final PipedOutputStream out = new PipedOutputStream();
final BufferedReader reader = new BufferedReader(new InputStreamReader(new PipedInputStream(out), StandardCharsets.UTF_8));
final BufferedReader reader = new BufferedReader(
new InputStreamReader(new PipedInputStream(out), StandardCharsets.UTF_8));
final ReadingRunnable runnable = new ReadingRunnable(reader);
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
final BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(out, StandardCharsets.UTF_8));
writer.write("one");
writer.newLine();
writer.write("two");
@ -222,4 +228,30 @@ public class ReadingRunnableTest {
// ok
}
}
@Test
public void testInterruption() throws IOException, InterruptedException {
final PipedOutputStream out = new PipedOutputStream();
final BufferedReader reader = new BufferedReader(
new InputStreamReader(new PipedInputStream(out), StandardCharsets.UTF_8));
final ReadingRunnable runnable = new ReadingRunnable(reader);
final FunctionalTestRunnable test = new FunctionalTestRunnable(
new TestFunction() {
@Override
public void apply() throws IOException {
try {
runnable.getMessage();
fail("Message interruption should cause an exception");
} catch (final InterruptedIOException e) {
// ok
}
}
});
final Thread th = new Thread(test, "Prompt wait");
th.start();
th.join(200);
runnable.interrupt();
ThreadTest.assertRuns(th, test);
}
}