Skip to content

Commit 2237113

Browse files
committed
Add a read/write test, and a stub identifier test.
The read/write test expects to be configured (via environment variables; see the `SerialPortExtension` Javadoc for details) with the names of two serial ports connected to each other with a null modem cable. Armed with this hardware, it verifies basic read/write functionality of the library across a range of baud rates, and using both block and byte transfers. The identifier test merely verifies a non-null response. Not terribly useful at present, but I hope to use it to validate that the ports configured for the other test are reported correctly (assuming that the user configured them correctly, of course).
1 parent 08c2fd4 commit 2237113

File tree

3 files changed

+552
-0
lines changed

3 files changed

+552
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package gnu.io;
2+
3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4+
5+
import java.util.Enumeration;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
class CommPortIdentifierTest
10+
{
11+
@Test
12+
void testGetCommPortIdentifiers()
13+
{
14+
@SuppressWarnings("rawtypes")
15+
Enumeration identifiers = CommPortIdentifier.getPortIdentifiers();
16+
assertNotNull(identifiers);
17+
18+
/* Hard to assert on anything else when the test hardware may change
19+
* dramatically.
20+
*
21+
* TODO: Wire up SerialPortExtension without its test-disabling
22+
* ExecutionCondition behaviour, and use the names of its configured
23+
* ports to confirm that the identifiers enumeration at least contains
24+
* something when we know what to expect in it. */
25+
}
26+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package gnu.io;
2+
3+
import java.io.Closeable;
4+
import java.io.IOException;
5+
import java.util.logging.Logger;
6+
7+
import org.junit.jupiter.api.condition.OS;
8+
import org.junit.jupiter.api.extension.AfterEachCallback;
9+
import org.junit.jupiter.api.extension.BeforeEachCallback;
10+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
11+
import org.junit.jupiter.api.extension.ExecutionCondition;
12+
import org.junit.jupiter.api.extension.ExtensionContext;
13+
14+
/**
15+
* Provisioning of serial ports for functionality testing.
16+
* <p>
17+
* This test extension automates the acquisition and resetting of two serial
18+
* ports for functional testing. Tests which use these ports assume that
19+
* they're connected to each other with a null modem cable so that data can be
20+
* exchanged between them. Then, so that this class will use them, set the
21+
* <code>NRJS_TEST_PORT_A</code> and <code>NRJS_TEST_PORT_B</code> environment
22+
* variables to the names of the ports prior to executing tests.
23+
* <p>
24+
* For example:
25+
*
26+
* <pre>
27+
* NRJS_TEST_PORT_A=/dev/ttyS0 NRJS_TEST_PORT_B=/dev/ttyS1 ./gradlew test
28+
* </pre>
29+
*
30+
* Or, on Windows:
31+
*
32+
* <pre>
33+
* set NRJS_TEST_PORT_A=COM1
34+
* set NRJS_TEST_PORT_B=COM2
35+
* gradlew.bat test
36+
* </pre>
37+
*/
38+
class SerialPortExtension implements Closeable, ExecutionCondition, BeforeEachCallback, AfterEachCallback
39+
{
40+
private static final Logger log = Logger.getLogger(SerialPortExtension.class.getName());
41+
42+
/** The environment variable holding the name of the first test port. */
43+
static final String A_PORT_ENV = "NRJS_TEST_PORT_A";
44+
/** The environment variable holding the name of the second test port. */
45+
static final String B_PORT_ENV = "NRJS_TEST_PORT_B";
46+
47+
/**
48+
* The owner name to pass to {@link CommPortIdentifier#open(String, int)}.
49+
*/
50+
private static final String PORT_OWNER = "NRJavaSerial - SerialPortExtension";
51+
/** The timeout to pass to {@link CommPortIdentifier#open(String, int)}. */
52+
private static final int OPEN_TIMEOUT = 1_000;
53+
54+
/**
55+
* When the test port configuration is sane, this is returned to indicate
56+
* that tests relying on serial ports should be enabled.
57+
*/
58+
private static final ConditionEvaluationResult HAS_PORTS = ConditionEvaluationResult
59+
.enabled("Two serial ports are available, and could be opened.");
60+
/**
61+
* When test ports aren't configured, this is returned to indicate that
62+
* tests relying on serial ports should be disabled.
63+
*/
64+
private static final ConditionEvaluationResult MISSING_PORTS = ConditionEvaluationResult
65+
.disabled("The system does not have two serial ports, or they could not be opened.");
66+
67+
/** Whether both test port identifiers have been populated. */
68+
final boolean hasIds;
69+
/**
70+
* The first test port identifier.
71+
* <p>
72+
* Populated at construction by searching for ports matching the name given
73+
* via the {@link SerialPortExtension#A_PORT_ENV} environment variable.
74+
*/
75+
final CommPortIdentifier aId;
76+
/**
77+
* The second test port identifier.
78+
* <p>
79+
* Populated at construction by searching for ports matching the name given
80+
* via the {@link SerialPortExtension#B_PORT_ENV} environment variable.
81+
*/
82+
final CommPortIdentifier bId;
83+
/**
84+
* The first test port.
85+
* <p>
86+
* Ports are opened from their corresponding identifiers prior to each
87+
* test, and are closed afterwards. This field will always be null outside
88+
* of the context of a test.
89+
*/
90+
SerialPort a = null;
91+
/**
92+
* The second test port.
93+
* <p>
94+
* Ports are opened from their corresponding identifiers prior to each
95+
* test, and are closed afterwards. This field will always be null outside
96+
* of the context of a test.
97+
*/
98+
SerialPort b = null;
99+
100+
public SerialPortExtension()
101+
{
102+
String aName = null;
103+
String bName = null;
104+
try
105+
{
106+
aName = System.getenv(SerialPortExtension.A_PORT_ENV);
107+
bName = System.getenv(SerialPortExtension.B_PORT_ENV);
108+
}
109+
catch (SecurityException e)
110+
{
111+
log.severe("Failed a security check while accessing the environment variable containing the port name: "
112+
+ e.getMessage());
113+
}
114+
115+
if (aName == null || bName == null)
116+
{
117+
String[] example;
118+
if (OS.WINDOWS.isCurrentOs())
119+
{
120+
example = new String[] { "set " + SerialPortExtension.A_PORT_ENV + "=COM1",
121+
"set " + SerialPortExtension.B_PORT_ENV + "=COM2", "gradlew.bat test" };
122+
}
123+
else
124+
{
125+
example = new String[] { SerialPortExtension.A_PORT_ENV + "=/dev/ttyUSB0 "
126+
+ SerialPortExtension.B_PORT_ENV + "=/dev/ttyUSB1 ./gradlew test" };
127+
}
128+
log.severe("The serial port functionality tests require the use of two ports. These should be connected to "
129+
+ "each other with a null modem cable. Then set the environment variables "
130+
+ SerialPortExtension.A_PORT_ENV + " and " + SerialPortExtension.B_PORT_ENV + " to the names of "
131+
+ "the ports, and re-run the tests. For example:\n\n\t" + String.join("\n\t", example));
132+
133+
this.hasIds = false;
134+
this.aId = null;
135+
this.bId = null;
136+
return;
137+
}
138+
139+
CommPortIdentifier aId;
140+
CommPortIdentifier bId;
141+
try
142+
{
143+
aId = CommPortIdentifier.getPortIdentifier(aName);
144+
bId = CommPortIdentifier.getPortIdentifier(bName);
145+
}
146+
catch (NoSuchPortException e)
147+
{
148+
log.severe("No such port: " + e.getMessage());
149+
150+
this.hasIds = false;
151+
this.aId = null;
152+
this.bId = null;
153+
return;
154+
}
155+
156+
log.info("Will attempt to use ports " + aId.getName() + " and " + bId.getName() + " for serial port "
157+
+ "functionality tests.");
158+
this.hasIds = true;
159+
this.aId = aId;
160+
this.bId = bId;
161+
}
162+
163+
@Override
164+
public void close()
165+
{
166+
if (this.a != null)
167+
{
168+
a.close();
169+
a = null;
170+
}
171+
if (this.b != null)
172+
{
173+
b.close();
174+
b = null;
175+
}
176+
}
177+
178+
@Override
179+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context)
180+
{
181+
return this.hasIds
182+
? SerialPortExtension.HAS_PORTS
183+
: SerialPortExtension.MISSING_PORTS;
184+
}
185+
186+
@Override
187+
public void beforeEach(ExtensionContext context) throws PortInUseException, NoSuchPortException
188+
{
189+
try
190+
{
191+
this.a = this.aId.open(SerialPortExtension.PORT_OWNER, SerialPortExtension.OPEN_TIMEOUT);
192+
this.b = this.bId.open(SerialPortExtension.PORT_OWNER, SerialPortExtension.OPEN_TIMEOUT);
193+
}
194+
catch (PortInUseException e)
195+
{
196+
log.severe("Port is in use: " + e.getMessage());
197+
this.close();
198+
throw e;
199+
}
200+
}
201+
202+
@Override
203+
public void afterEach(ExtensionContext context) throws IOException
204+
{
205+
this.close();
206+
}
207+
}

0 commit comments

Comments
 (0)