-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add control line tests (DTR/DSR and RTS/CTS).
- Loading branch information
Showing
1 changed file
with
169 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |