Stabilized the browser shutdown process.
This commit is contained in:
@@ -6,6 +6,7 @@ package edu.regis.universeplayer.addon;
|
|||||||
|
|
||||||
import edu.regis.universeplayer.browserCommands.BrowserConstants;
|
import edu.regis.universeplayer.browserCommands.BrowserConstants;
|
||||||
import edu.regis.universeplayer.browserCommands.MessageHandler;
|
import edu.regis.universeplayer.browserCommands.MessageHandler;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ import java.util.concurrent.Future;
|
|||||||
public class Main
|
public class Main
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Main.class);
|
private static final Logger logger = LoggerFactory.getLogger(Main.class);
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException
|
public static void main(String[] args) throws IOException
|
||||||
{
|
{
|
||||||
MessageHandler interfaceLink;
|
MessageHandler interfaceLink;
|
||||||
@@ -32,14 +33,22 @@ public class Main
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.debug("Setting up connection");
|
logger.debug("Setting up connection");
|
||||||
socket = new Socket(BrowserConstants.IP,BrowserConstants.PORT);
|
socket = new Socket(BrowserConstants.IP, BrowserConstants.PORT);
|
||||||
logger.debug("Connection established");
|
logger.debug("Connection established");
|
||||||
|
|
||||||
Socket finalSocket = socket;
|
Socket finalSocket = socket;
|
||||||
interfaceLink = new MessageHandler("InterfaceHandler", finalSocket.getInputStream(), finalSocket.getOutputStream()) {
|
interfaceLink = new MessageHandler("InterfaceHandler", finalSocket.getInputStream(), finalSocket.getOutputStream())
|
||||||
|
{
|
||||||
|
private long lastPing = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onRun()
|
protected boolean onRun()
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* How many milliseconds must pass between browser
|
||||||
|
* pings.
|
||||||
|
*/
|
||||||
|
final long PING_RATE = 5000;
|
||||||
if (!finalSocket.isConnected() || finalSocket.isClosed() || finalSocket.isInputShutdown() || finalSocket.isOutputShutdown())
|
if (!finalSocket.isConnected() || finalSocket.isClosed() || finalSocket.isInputShutdown() || finalSocket.isOutputShutdown())
|
||||||
{
|
{
|
||||||
logger.debug("Interface socket closed, shutting down");
|
logger.debug("Interface socket closed, shutting down");
|
||||||
@@ -48,10 +57,22 @@ public class Main
|
|||||||
/*
|
/*
|
||||||
* Make sure that it is active.
|
* Make sure that it is active.
|
||||||
*/
|
*/
|
||||||
// this.sendUpdate("ping");
|
if (SocketAppender.hasLogs())
|
||||||
|
{
|
||||||
|
lastPing = System.currentTimeMillis();
|
||||||
|
for (LogEvent event : SocketAppender.retrieveLogEvents())
|
||||||
|
{
|
||||||
|
this.sendUpdate(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (System.currentTimeMillis() - lastPing >= PING_RATE)
|
||||||
|
{
|
||||||
|
lastPing = System.currentTimeMillis();
|
||||||
|
this.sendUpdate("ping");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onClose()
|
protected void onClose()
|
||||||
{
|
{
|
||||||
@@ -72,20 +93,22 @@ public class Main
|
|||||||
* Pretty much just forwards any messages to the browser and
|
* Pretty much just forwards any messages to the browser and
|
||||||
* returns their value.
|
* returns their value.
|
||||||
*/
|
*/
|
||||||
browserLink.addUpdateListener((update, link) -> {
|
browserLink.addUpdateListener((update, link) ->
|
||||||
|
{
|
||||||
logger.debug("Sending update {}", update);
|
logger.debug("Sending update {}", update);
|
||||||
interfaceLink.sendUpdate(update);
|
interfaceLink.sendUpdate(update);
|
||||||
});
|
});
|
||||||
interfaceLink.addListener((providedValue, previousReturn) -> {
|
interfaceLink.addListener((providedValue, previousReturn) ->
|
||||||
|
{
|
||||||
logger.debug("Forwarding message to browser: {}", providedValue);
|
logger.debug("Forwarding message to browser: {}", providedValue);
|
||||||
Object returnValue = browserLink.sendObject(providedValue).get();
|
Object returnValue = browserLink.sendObject(providedValue).get();
|
||||||
logger.debug("Forwarding return to interface: {}", returnValue);
|
logger.debug("Forwarding return to interface: {}", returnValue);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
browserThread = new Thread(browserLink);
|
browserThread = new Thread(browserLink);
|
||||||
interfaceThread = new Thread(interfaceLink);
|
interfaceThread = new Thread(interfaceLink);
|
||||||
|
|
||||||
logger.debug("Starting threads.");
|
logger.debug("Starting threads.");
|
||||||
browserThread.start();
|
browserThread.start();
|
||||||
interfaceThread.start();
|
interfaceThread.start();
|
||||||
@@ -136,7 +159,7 @@ public class Main
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void testMain() throws ExecutionException, InterruptedException
|
private static void testMain() throws ExecutionException, InterruptedException
|
||||||
{
|
{
|
||||||
LinkedList<Future<Object>> requests = new LinkedList<>();
|
LinkedList<Future<Object>> requests = new LinkedList<>();
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.core.*;
|
||||||
|
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||||
|
import org.apache.logging.log4j.core.config.Property;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This appender will store all messages sent to it, for the interface link to
|
||||||
|
* later collect and forward to the interface.
|
||||||
|
*
|
||||||
|
* @author William Hubbard
|
||||||
|
*/
|
||||||
|
@Plugin(
|
||||||
|
name = "Socket",
|
||||||
|
category = Core.CATEGORY_NAME,
|
||||||
|
elementType = Appender.ELEMENT_TYPE)
|
||||||
|
public class SocketAppender extends AbstractAppender
|
||||||
|
{
|
||||||
|
private static final LinkedList<LogEvent> events = new LinkedList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds FileAppender instances.
|
||||||
|
*
|
||||||
|
* @param <B> The type to build
|
||||||
|
*/
|
||||||
|
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
|
||||||
|
implements org.apache.logging.log4j.core.util.Builder<SocketAppender>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public SocketAppender build()
|
||||||
|
{
|
||||||
|
return new SocketAppender(getName(), getFilter(), getOrCreateLayout(), isIgnoreExceptions(), getPropertyArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginBuilderFactory
|
||||||
|
public static <B extends Builder<B>> B newBuilder()
|
||||||
|
{
|
||||||
|
return new Builder<B>().asBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
|
||||||
|
final boolean ignoreExceptions, final Property[] properties)
|
||||||
|
{
|
||||||
|
super(name, filter, layout, ignoreExceptions, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a LogEvent using whatever logic this Appender wishes to use. It is
|
||||||
|
* typically recommended to use a bridge pattern not only for the benefits
|
||||||
|
* from decoupling an Appender from its implementation, but it is also handy
|
||||||
|
* for sharing resources which may require some form of locking.
|
||||||
|
*
|
||||||
|
* @param event The LogEvent.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void append(LogEvent event)
|
||||||
|
{
|
||||||
|
synchronized (events)
|
||||||
|
{
|
||||||
|
events.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if any logs are available.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean hasLogs()
|
||||||
|
{
|
||||||
|
synchronized (events)
|
||||||
|
{
|
||||||
|
return !events.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<LogEvent> retrieveLogEvents()
|
||||||
|
{
|
||||||
|
synchronized (events)
|
||||||
|
{
|
||||||
|
List<LogEvent> logs = events.stream().toList();
|
||||||
|
events.clear();
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
browser/src/test/resources/log4j2.xml
Normal file
17
browser/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
-->
|
||||||
|
<Configuration packages="edu.regis.universeplayer.browser" status="WARN">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_ERR">
|
||||||
|
<PatternLayout pattern="%c:%L %-5level - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="debug">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
<Logger name="BrowserRunner" level="info"/>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package edu.regis.universeplayer;
|
||||||
|
|
||||||
|
public class AutoGson
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -79,7 +79,8 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
BufferedInputStream browserIn = null;
|
BufferedInputStream browserIn = null;
|
||||||
BufferedOutputStream browserOut = null;
|
BufferedOutputStream browserOut = null;
|
||||||
MessagePacket packet;
|
MessagePacket packet;
|
||||||
|
|
||||||
|
boolean running = true;
|
||||||
int messageNum = -1;
|
int messageNum = -1;
|
||||||
byte[][] returnMessage;
|
byte[][] returnMessage;
|
||||||
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
||||||
@@ -89,7 +90,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
browserIn = new BufferedInputStream(this.input);
|
browserIn = new BufferedInputStream(this.input);
|
||||||
browserOut = new BufferedOutputStream(this.output);
|
browserOut = new BufferedOutputStream(this.output);
|
||||||
|
|
||||||
while (!this.onRun())
|
while (running && !this.onRun())
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Sends a messages
|
* Sends a messages
|
||||||
@@ -104,17 +105,19 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
{
|
{
|
||||||
messageNum = this.messagesSent;
|
messageNum = this.messagesSent;
|
||||||
packet.returnValue.index = messageNum;
|
packet.returnValue.index = messageNum;
|
||||||
writeMessage(browserOut, messageNum, packet.message);
|
|
||||||
|
|
||||||
synchronized (this.sentQueue)
|
synchronized (this.sentQueue)
|
||||||
{
|
{
|
||||||
this.sentQueue.put(messageNum, packet);
|
this.sentQueue.put(messageNum, packet);
|
||||||
this.messagesSent++;
|
this.messagesSent++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeMessage(browserOut, messageNum, packet.message);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not send message " + messageNum + " " + new String(packet.message, StandardCharsets.UTF_8), e);
|
logger.error("Could not send message {} {}", messageNum, new String(packet.message, StandardCharsets.UTF_8), e);
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +186,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not retrieve message", e);
|
logger.error("Could not retrieve message", e);
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,14 +199,11 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
/*
|
/*
|
||||||
* Release locks for any messages still waiting.
|
* Release locks for any messages still waiting.
|
||||||
*/
|
*/
|
||||||
synchronized (this.sendQueue)
|
synchronized (this.sentQueue)
|
||||||
{
|
{
|
||||||
logger.debug("Clearing up {} messages", this.sendQueue.size());
|
logger.debug("Clearing up {} messages", this.sentQueue.size());
|
||||||
while (this.sendQueue.size() > 0)
|
this.sentQueue.values().forEach(foundPacket -> foundPacket.returnMessage = new byte[0]);
|
||||||
{
|
this.sentQueue.clear();
|
||||||
packet = this.sendQueue.poll();
|
|
||||||
packet.returnMessage = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
synchronized (this.readLock)
|
synchronized (this.readLock)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package edu.regis.universeplayer.browserCommands;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class MessageRunnerTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The input to the runner. This corresponds to the output from the
|
||||||
|
* handler.
|
||||||
|
*
|
||||||
|
* @see #handlerOut
|
||||||
|
*/
|
||||||
|
private static PipedInputStream runnerIn;
|
||||||
|
/**
|
||||||
|
* The output from the handler and to the runner. This corresponds to the
|
||||||
|
* input to the runner.
|
||||||
|
*
|
||||||
|
* @see #runnerIn
|
||||||
|
*/
|
||||||
|
private static PipedOutputStream handlerOut;
|
||||||
|
/**
|
||||||
|
* The input to the handler from the runner. This corresponds to the output
|
||||||
|
* from the runner.
|
||||||
|
*
|
||||||
|
* @see #runnerOut
|
||||||
|
*/
|
||||||
|
private static PipedInputStream handlerIn;
|
||||||
|
/**
|
||||||
|
* The output from the runner and to the handler. This corresponds to the
|
||||||
|
* input to the handler.
|
||||||
|
*
|
||||||
|
* @see #handlerIn
|
||||||
|
*/
|
||||||
|
private static PipedOutputStream runnerOut;
|
||||||
|
private static TestRunner runner;
|
||||||
|
private static TestHandler handler;
|
||||||
|
private static Thread runnerThread;
|
||||||
|
private static Thread handlerThread;
|
||||||
|
|
||||||
|
private static class TestRunner extends MessageRunner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a message runner.
|
||||||
|
*
|
||||||
|
* @param name - The name of the runner. This is used in logging.
|
||||||
|
* @param input - The input from our external source.
|
||||||
|
* @param output - The output to the external source.
|
||||||
|
*/
|
||||||
|
public TestRunner(String name, InputStream input, OutputStream output)
|
||||||
|
{
|
||||||
|
super(name, input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestHandler extends MessageHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a message handler.
|
||||||
|
*
|
||||||
|
* @param name - The name of the handler. This is used in logging.
|
||||||
|
* @param input - The input from our external source.
|
||||||
|
* @param output - The output to the external source.
|
||||||
|
*/
|
||||||
|
public TestHandler(String name, InputStream input, OutputStream output)
|
||||||
|
{
|
||||||
|
super(name, input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void before()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runnerIn = new PipedInputStream();
|
||||||
|
handlerOut = new PipedOutputStream(runnerIn);
|
||||||
|
handlerIn = new PipedInputStream();
|
||||||
|
runnerOut = new PipedOutputStream(handlerIn);
|
||||||
|
|
||||||
|
runner = new TestRunner("TestRunner", runnerIn, runnerOut);
|
||||||
|
handler = new TestHandler("TestHandler", handlerIn, handlerOut);
|
||||||
|
|
||||||
|
runnerThread = new Thread(runner);
|
||||||
|
handlerThread = new Thread(handler);
|
||||||
|
runnerThread.start();
|
||||||
|
handlerThread.start();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessage() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
String mess = "Hello, world!";
|
||||||
|
String returnMess = "Goodnight, moon!";
|
||||||
|
MessageHandler.MessageListener listener = new MessageHandler.MessageListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object onMessage(Object providedValue, Object previousReturn) throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
assertEquals(mess, providedValue);
|
||||||
|
return returnMess;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler.addListener(listener);
|
||||||
|
Future<Object> messFuture = runner.sendObject(mess);
|
||||||
|
assertEquals(returnMess, messFuture.get());
|
||||||
|
handler.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests messages that arrive in a different order than they were sent.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAsyncMessage() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
final String mess1 = "Hello, world!";
|
||||||
|
final String mess2 = "Wake up, sun!";
|
||||||
|
final String mess3 = "Good morning, house!";
|
||||||
|
final String returnMess1 = "Goodnight, moon!";
|
||||||
|
final String returnMess2 = "Good evening, star!";
|
||||||
|
final String returnMess3 = "Goodbye, friend!";
|
||||||
|
MessageHandler.MessageListener listener = new MessageHandler.MessageListener()
|
||||||
|
{
|
||||||
|
AtomicBoolean received2 = new AtomicBoolean();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object onMessage(Object providedValue, Object previousReturn) throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
assertTrue(providedValue instanceof String);
|
||||||
|
switch ((String) providedValue)
|
||||||
|
{
|
||||||
|
case mess1:
|
||||||
|
synchronized (received2)
|
||||||
|
{
|
||||||
|
while (!received2.get())
|
||||||
|
{
|
||||||
|
received2.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnMess1;
|
||||||
|
case mess3:
|
||||||
|
synchronized (received2)
|
||||||
|
{
|
||||||
|
received2.set(true);
|
||||||
|
received2.notifyAll();
|
||||||
|
}
|
||||||
|
return returnMess3;
|
||||||
|
case mess2:
|
||||||
|
return returnMess2;
|
||||||
|
default:
|
||||||
|
Assert.fail("Expected \"" + mess1 + "\" or \"" + mess1 + "\", actual \"" + providedValue + "\"");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler.addListener(listener);
|
||||||
|
Future<Object> messFuture = runner.sendObject(mess1);
|
||||||
|
Thread.sleep(250);
|
||||||
|
assertFalse(messFuture.isDone());
|
||||||
|
Future<Object> messFuture2 = runner.sendObject(mess2);
|
||||||
|
Thread.sleep(250);
|
||||||
|
assertFalse(messFuture.isDone());
|
||||||
|
assertTrue(messFuture2.isDone());
|
||||||
|
Future<Object> messFuture3 = runner.sendObject(mess3);
|
||||||
|
Thread.sleep(250);
|
||||||
|
assertTrue(messFuture.isDone());
|
||||||
|
assertTrue(messFuture2.isDone());
|
||||||
|
assertTrue(messFuture3.isDone());
|
||||||
|
assertEquals(returnMess1, messFuture.get());
|
||||||
|
assertEquals(returnMess2, messFuture2.get());
|
||||||
|
assertEquals(returnMess3, messFuture3.get());
|
||||||
|
handler.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the handler's update feature.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdate() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
final String mess1 = "Hello, world!";
|
||||||
|
final AtomicBoolean received = new AtomicBoolean();
|
||||||
|
UpdateListener listener = (object, runner) ->
|
||||||
|
{
|
||||||
|
assertEquals(mess1, object);
|
||||||
|
synchronized (received)
|
||||||
|
{
|
||||||
|
received.set(true);
|
||||||
|
received.notifyAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
runner.addUpdateListener(listener);
|
||||||
|
handler.sendUpdate(mess1);
|
||||||
|
synchronized (received)
|
||||||
|
{
|
||||||
|
while (!received.get())
|
||||||
|
{
|
||||||
|
received.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runner.removeUpdateListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void closeRunner() throws IOException
|
||||||
|
{
|
||||||
|
runnerOut.close();
|
||||||
|
runnerIn.close();
|
||||||
|
handler.sendUpdate("ping");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package edu.regis.universeplayer.browserCommands;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
public class RunnerCloseTest
|
||||||
|
{
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(RunnerCloseTest.class);
|
||||||
|
/**
|
||||||
|
* The input to the runner. This corresponds to the output from the
|
||||||
|
* handler.
|
||||||
|
*
|
||||||
|
* @see #handlerOut
|
||||||
|
*/
|
||||||
|
private PipedInputStream runnerIn;
|
||||||
|
/**
|
||||||
|
* The output from the handler and to the runner. This corresponds to the
|
||||||
|
* input to the runner.
|
||||||
|
*
|
||||||
|
* @see #runnerIn
|
||||||
|
*/
|
||||||
|
private PipedOutputStream handlerOut;
|
||||||
|
/**
|
||||||
|
* The input to the handler from the runner. This corresponds to the output
|
||||||
|
* from the runner.
|
||||||
|
*
|
||||||
|
* @see #runnerOut
|
||||||
|
*/
|
||||||
|
private PipedInputStream handlerIn;
|
||||||
|
/**
|
||||||
|
* The output from the runner and to the handler. This corresponds to the
|
||||||
|
* input to the handler.
|
||||||
|
*
|
||||||
|
* @see #handlerIn
|
||||||
|
*/
|
||||||
|
private PipedOutputStream runnerOut;
|
||||||
|
private TestRunner runner;
|
||||||
|
private TestHandler handler;
|
||||||
|
private Thread runnerThread;
|
||||||
|
private Thread handlerThread;
|
||||||
|
|
||||||
|
private class TestRunner extends MessageRunner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a message runner.
|
||||||
|
*
|
||||||
|
* @param name - The name of the runner. This is used in logging.
|
||||||
|
* @param input - The input from our external source.
|
||||||
|
* @param output - The output to the external source.
|
||||||
|
*/
|
||||||
|
public TestRunner(String name, InputStream input, OutputStream output)
|
||||||
|
{
|
||||||
|
super(name, input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHandler extends MessageHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a message handler.
|
||||||
|
*
|
||||||
|
* @param name - The name of the handler. This is used in logging.
|
||||||
|
* @param input - The input from our external source.
|
||||||
|
* @param output - The output to the external source.
|
||||||
|
*/
|
||||||
|
public TestHandler(String name, InputStream input, OutputStream output)
|
||||||
|
{
|
||||||
|
super(name, input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runnerIn = new PipedInputStream();
|
||||||
|
handlerOut = new PipedOutputStream(runnerIn);
|
||||||
|
handlerIn = new PipedInputStream();
|
||||||
|
runnerOut = new PipedOutputStream(handlerIn);
|
||||||
|
|
||||||
|
runner = new TestRunner("TestRunner", runnerIn, runnerOut);
|
||||||
|
handler = new TestHandler("TestHandler", handlerIn, handlerOut);
|
||||||
|
|
||||||
|
handler.addListener((providedValue, previousReturn) -> "ping");
|
||||||
|
|
||||||
|
runnerThread = new Thread(runner);
|
||||||
|
handlerThread = new Thread(handler);
|
||||||
|
runnerThread.start();
|
||||||
|
handlerThread.start();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sees what happens when the runner is closed but sends a message anyway.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRunnerClose() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
logger.info("Testing runner close with message");
|
||||||
|
runnerIn.close();
|
||||||
|
runnerOut.close();
|
||||||
|
assertNull(runner.sendObject("pong").get());
|
||||||
|
logger.info("Runner test complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sees what happens when the runner is closed and an update is sent from
|
||||||
|
* the handler.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRunnerCloseUpdate() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
logger.info("Testing runner close with update");
|
||||||
|
runnerIn.close();
|
||||||
|
runnerOut.close();
|
||||||
|
handler.sendUpdate("ping");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
logger.info("Runner test complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sees what happens when the handler is closed and the runner attempts to
|
||||||
|
* send a message.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testHandlerClose() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
logger.info("Testing handler close with message");
|
||||||
|
handlerIn.close();
|
||||||
|
handlerOut.close();
|
||||||
|
assertNull(runner.sendObject("pong").get());
|
||||||
|
logger.info("Handler test complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sees what happens when the handler is closed but attempts to send an
|
||||||
|
* update anyway.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ExecutionException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testHandlerCloseUpdate() throws IOException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
logger.info("Testing handler close with update");
|
||||||
|
handlerIn.close();
|
||||||
|
handlerOut.close();
|
||||||
|
handler.sendUpdate("ping");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
logger.info("Handler test complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
browserCommands/src/test/resources/log4j2.xml
Normal file
17
browserCommands/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
-->
|
||||||
|
<Configuration packages="edu.regis.universeplayer.browserCommands" status="WARN">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_ERR">
|
||||||
|
<PatternLayout pattern="%c:%L %-5level - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="debug">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
<Logger name="BrowserRunner" level="info"/>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
Reference in New Issue
Block a user