Changed prompt interruption contract
Signed-off-by: Emmanuel Bigeon <emmanuel@bigeon.fr>
This commit is contained in:
parent
c4bbfd8434
commit
c206b5b22c
@ -56,6 +56,11 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.12</version>
|
<version>4.12</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.bigeon.test</groupId>
|
||||||
|
<artifactId>junitmt</artifactId>
|
||||||
|
<version>1.0.2</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<reporting>
|
<reporting>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
@ -8,18 +8,19 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import net.bigeon.gclc.manager.ConsoleInput;
|
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 final class InputContract {
|
||||||
|
|
||||||
public void testInputContract(final Supplier<ConsoleInput> inputs)
|
public void testInputContract(final Supplier<ConsoleInput> inputs)
|
||||||
throws IOException {
|
throws IOException, InterruptedException {
|
||||||
// Test close contract
|
// Test close contract
|
||||||
final ConsoleInput input = inputs.get();
|
final ConsoleInput input = inputs.get();
|
||||||
assertFalse("An input should not initially be closed", input.isClosed());
|
assertFalse("An input should not initially be closed", input.isClosed());
|
||||||
@ -33,5 +34,29 @@ public final class InputContract {
|
|||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
// ok
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,8 +94,7 @@ public interface ConsoleInput extends AutoCloseable {
|
|||||||
/** Indicate to the input that is should interrompt the prompting, if possible.
|
/** Indicate to the input that is should interrompt the prompting, if possible.
|
||||||
* <p>
|
* <p>
|
||||||
* The pending {@link #prompt()} or {@link #prompt(String)} operations should
|
* The pending {@link #prompt()} or {@link #prompt(String)} operations should
|
||||||
* return immediatly. However the returned value can be anything (from the
|
* return immediately by throwing an InterruptedIOException. */
|
||||||
* partial prompt content to an empty string or even a null pointer). */
|
|
||||||
void interruptPrompt();
|
void interruptPrompt();
|
||||||
|
|
||||||
/** Test if the input is closed.
|
/** Test if the input is closed.
|
||||||
|
@ -111,6 +111,7 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
private final Map<String, Object> messageBlocker = new ConcurrentHashMap<>();
|
private final Map<String, Object> messageBlocker = new ConcurrentHashMap<>();
|
||||||
/** The lock. */
|
/** The lock. */
|
||||||
private final Object messageBlockerLock = new Object();
|
private final Object messageBlockerLock = new Object();
|
||||||
|
private boolean interrupting = false;
|
||||||
/** Create a reading runnable.
|
/** Create a reading runnable.
|
||||||
*
|
*
|
||||||
* @param reader the input to read from */
|
* @param reader the input to read from */
|
||||||
@ -146,8 +147,7 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
public String getMessage() throws IOException {
|
public String getMessage() throws IOException {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (!messages.isEmpty()) {
|
if (!messages.isEmpty()) {
|
||||||
notifyMessage(messages.peek());
|
return pollMessage();
|
||||||
return messages.poll();
|
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!running) {
|
||||||
throw new IOException(CLOSED_PIPE);
|
throw new IOException(CLOSED_PIPE);
|
||||||
@ -156,8 +156,7 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
waitMessage(TIMEOUT);
|
waitMessage(TIMEOUT);
|
||||||
LOGGER.log(Level.FINEST, "Polled: {0}", messages.peek()); //$NON-NLS-1$
|
LOGGER.log(Level.FINEST, "Polled: {0}", messages.peek()); //$NON-NLS-1$
|
||||||
waiting = false;
|
waiting = false;
|
||||||
notifyMessage(messages.peek());
|
return pollMessage();
|
||||||
return messages.poll();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +168,7 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
public String getNextMessage(final long timeout) throws IOException {
|
public String getNextMessage(final long timeout) throws IOException {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (!messages.isEmpty()) {
|
if (!messages.isEmpty()) {
|
||||||
notifyMessage(messages.peek());
|
return pollMessage();
|
||||||
return messages.poll();
|
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!running) {
|
||||||
throw new IOException(CLOSED_PIPE);
|
throw new IOException(CLOSED_PIPE);
|
||||||
@ -181,11 +179,19 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
if (messages.isEmpty()) {
|
if (messages.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
notifyMessage(messages.peek());
|
return pollMessage();
|
||||||
return messages.poll();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/** Wait for a message to be delivered.
|
||||||
*
|
*
|
||||||
* @param message the message
|
* @param message the message
|
||||||
@ -221,7 +227,8 @@ public final class ReadingRunnable implements Runnable {
|
|||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (waiting) {
|
if (waiting) {
|
||||||
messages.offer(""); //$NON-NLS-1$
|
messages.offer("");
|
||||||
|
interrupting = true;
|
||||||
lock.notifyAll();
|
lock.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ import java.io.BufferedWriter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PipedInputStream;
|
import java.io.PipedInputStream;
|
||||||
import java.io.PipedOutputStream;
|
import java.io.PipedOutputStream;
|
||||||
@ -90,6 +91,9 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import net.bigeon.gclc.utils.ReadingRunnable;
|
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>
|
* <p>
|
||||||
@ -198,9 +202,11 @@ public class ReadingRunnableTest {
|
|||||||
public final void testGetPendingMessages() throws IOException, InterruptedException {
|
public final void testGetPendingMessages() throws IOException, InterruptedException {
|
||||||
|
|
||||||
final PipedOutputStream out = new PipedOutputStream();
|
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 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.write("one");
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
writer.write("two");
|
writer.write("two");
|
||||||
@ -222,4 +228,30 @@ public class ReadingRunnableTest {
|
|||||||
// ok
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user