Skip to content

Commit

Permalink
Add control line tests (DTR/DSR and RTS/CTS).
Browse files Browse the repository at this point in the history
  • Loading branch information
MrDOS committed Jun 17, 2020
1 parent 7c316dd commit 13876df
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions src/test/java/gnu/io/SerialPortControlTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package gnu.io;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.TooManyListenersException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

/**
* Test the ability of the {@link SerialPort} implementation to manage the
* control lines of the port (DTR/DSR and RTS/CTS).
* <p>
* This test is nonspecific as to <em>which</em> implementation; it exercises
* only the public interface of the Java Communications API. Ports are opened
* by the {@link SerialPortExtension} test extension, presumably by way of
* {@link CommPortIdentifier}.
*/
public class SerialPortControlTest
{
private static final Logger log = Logger.getLogger(SerialPortControlTest.class.getName());

private static final String BUGGY_DSR_EVENTS = "DSR event propagation is buggy! DSR events are useless until this is fixed.";
private static final String BUGGY_CTS_EVENTS = "CTS event propagation is buggy! CTS events are useless until this is fixed.";

/**
* How long to wait (in milliseconds) for asynchronous events to arrive
* before failing the test.
*/
private static final long EVENT_TIMEOUT = 250;

@RegisterExtension
SerialPortExtension ports = new SerialPortExtension();

/**
* Test that toggling DTR on a port changes the DSR state of another.
*/
@Test
void testDTRDSRPolling()
{
this.ports.a.setDTR(false);
assertFalse(this.ports.a.isDTR());
assertFalse(this.ports.b.isDSR());

this.ports.a.setDTR(true);
assertTrue(this.ports.a.isDTR());
assertTrue(this.ports.b.isDSR());

this.ports.a.setDTR(false);
assertFalse(this.ports.a.isDTR());
assertFalse(this.ports.b.isDSR());
}

/**
* Test that toggling RTS on a port changes the CTS state of another.
*/
@Test
void testRTSCTSPolling()
{
this.ports.a.setRTS(false);
assertFalse(this.ports.a.isRTS());
assertFalse(this.ports.b.isCTS());

this.ports.a.setRTS(true);
assertTrue(this.ports.a.isRTS());
assertTrue(this.ports.b.isCTS());

this.ports.a.setRTS(false);
assertFalse(this.ports.a.isRTS());
assertFalse(this.ports.b.isCTS());
}

/* Use of CountDownLatch in the asynchronous event tests is based on
* https://stackoverflow.com/a/1829949/640170. */

/**
* Test that toggling DTR on a port changes generates a DSR event on
* another.
*
* @throws TooManyListenersException if the port has not been properly
* cleaned up after previous tests
* @throws InterruptedException if the test is interrupted while waiting
* for the event
*/
@Test
void testDTRDSREvents() throws TooManyListenersException, InterruptedException
{
CountDownLatch latch = new CountDownLatch(1);
this.ports.b.addEventListener(ev -> {
if (ev.getEventType() == SerialPortEvent.DSR)
{
latch.countDown();
}
});
this.ports.b.notifyOnDSR(true);
this.ports.a.setDTR(true);
boolean sawEvent = false;
sawEvent = latch.await(SerialPortControlTest.EVENT_TIMEOUT, TimeUnit.MILLISECONDS);
if (!sawEvent)
{
/* FIXME: The hard part about adding tests is that sometimes you
* find bugs. The DSR event _is_ generated: set a breakpoint in the
* event callback, or a print statement, and it will reliably fire
* – but only _after_ this await. The callback _is_ run from the
* monitor thread, as you'd expect it to be, and without the await,
* the listener is called within milliseconds of DTR being
* asserted; but block the main thread (with this await, or with
* `Thread.sleep()`), and the event will never happen.
*
* I'm mystified by this behaviour, because the callback
* configuration here is the same as in `testRTSCTSEvents()`, where
* it works as expected.
*
* One thing which does seem to cause the event to propagate is
* de-asserting DTR. Then _that_ event gets lost, of course, but it
* seems to prompt the first event to make its way through. So
* that's the workaround employed here for now; but this really is
* papering over a hole. The DSR event being one edge late means
* it's basically impossible for any consumer to hand-roll their
* own hardware flow control.
*
* This is broken on Windows (10), macOS (10.15), and Linux
* (4.19.0). */
log.warning(SerialPortControlTest.BUGGY_DSR_EVENTS);
this.ports.a.setDTR(false);
sawEvent = latch.await(SerialPortControlTest.EVENT_TIMEOUT, TimeUnit.MILLISECONDS);
}
assertTrue(sawEvent);
}

/**
* Test that toggling RTS on a port changes generates a CTS event on
* another.
*
* @throws TooManyListenersException if the port has not been properly
* cleaned up after previous tests
* @throws InterruptedException if the test is interrupted while waiting
* for the event
*/
@Test
void testRTSCTSEvents() throws TooManyListenersException, InterruptedException
{
CountDownLatch latch = new CountDownLatch(1);
this.ports.b.addEventListener(ev -> {
if (ev.getEventType() == SerialPortEvent.CTS)
{
latch.countDown();
}
});
this.ports.b.notifyOnCTS(true);
this.ports.a.setRTS(true);
boolean sawEvent = false;
sawEvent = latch.await(SerialPortControlTest.EVENT_TIMEOUT, TimeUnit.MILLISECONDS);
if (!sawEvent)
{
/* FIXME: Same story here as with DSR events. This works correctly
* on Windows (10), but fails on macOS (10.15) and Linux
* (4.19.0). */
log.warning(SerialPortControlTest.BUGGY_CTS_EVENTS);
this.ports.a.setRTS(false);
sawEvent = latch.await(SerialPortControlTest.EVENT_TIMEOUT, TimeUnit.MILLISECONDS);
}
assertTrue(sawEvent);
}
}

0 comments on commit 13876df

Please sign in to comment.