Skip to content

Commit fa60cbb

Browse files
authored
Add comprehensive unit tests for I/O utilities (#3996)
* Add comprehensive unit tests for I/O utilities * Fix socket and URL unit tests
1 parent 0dc108c commit fa60cbb

File tree

4 files changed

+657
-0
lines changed

4 files changed

+657
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package com.codename1.io;
2+
3+
import com.codename1.impl.CodenameOneImplementation;
4+
import com.codename1.ui.Display;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.InputStream;
9+
import java.io.OutputStream;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.ArrayList;
12+
import java.util.Arrays;
13+
import java.util.List;
14+
import java.util.concurrent.ConcurrentLinkedQueue;
15+
import java.util.concurrent.CountDownLatch;
16+
import java.util.concurrent.TimeUnit;
17+
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.concurrent.atomic.AtomicReference;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.ArgumentMatchers.anyInt;
23+
import static org.mockito.ArgumentMatchers.anyString;
24+
import static org.mockito.ArgumentMatchers.eq;
25+
import static org.mockito.Mockito.doAnswer;
26+
import static org.mockito.Mockito.when;
27+
28+
class SocketTest {
29+
private CodenameOneImplementation implementation;
30+
31+
@BeforeEach
32+
void setUp() {
33+
implementation = TestImplementationProvider.installImplementation(true);
34+
Display.getInstance();
35+
}
36+
37+
@Test
38+
void supportedFlagsDelegateToImplementation() {
39+
when(implementation.isSocketAvailable()).thenReturn(true);
40+
assertTrue(Socket.isSupported());
41+
when(implementation.isSocketAvailable()).thenReturn(false);
42+
assertFalse(Socket.isSupported());
43+
44+
when(implementation.isServerSocketAvailable()).thenReturn(true);
45+
assertTrue(Socket.isServerSocketSupported());
46+
}
47+
48+
@Test
49+
void connectRejectsHostsContainingPort() {
50+
SocketConnection connection = new SocketConnection() {
51+
public void connectionError(int errorCode, String message) {
52+
}
53+
54+
public void connectionEstablished(InputStream is, OutputStream os) {
55+
}
56+
};
57+
assertThrows(IllegalArgumentException.class, () -> Socket.connect("example.com:8080", 80, connection));
58+
}
59+
60+
@Test
61+
void connectFailureInvokesErrorCallback() throws InterruptedException {
62+
when(implementation.connectSocket(anyString(), anyInt(), anyInt())).thenReturn(null);
63+
CountDownLatch latch = new CountDownLatch(1);
64+
AtomicInteger errorCode = new AtomicInteger();
65+
AtomicReference<String> message = new AtomicReference<String>();
66+
SocketConnection connection = new SocketConnection() {
67+
public void connectionError(int code, String msg) {
68+
errorCode.set(code);
69+
message.set(msg);
70+
latch.countDown();
71+
}
72+
73+
public void connectionEstablished(InputStream is, OutputStream os) {
74+
fail("Should not connect");
75+
}
76+
};
77+
78+
Socket.connect("unreachable", 8080, connection);
79+
assertTrue(latch.await(2, TimeUnit.SECONDS));
80+
assertFalse(connection.isConnected());
81+
assertEquals(-1, errorCode.get());
82+
assertEquals("Failed to connect", message.get());
83+
}
84+
85+
@Test
86+
void connectEstablishesStreamsAndAllowsReadWrite() throws Exception {
87+
FakeSocketState state = prepareSocketState("example.com", 1234);
88+
CountDownLatch latch = new CountDownLatch(1);
89+
AtomicReference<InputStream> inputRef = new AtomicReference<InputStream>();
90+
AtomicReference<OutputStream> outputRef = new AtomicReference<OutputStream>();
91+
92+
SocketConnection connection = new SocketConnection() {
93+
public void connectionError(int errorCode, String message) {
94+
fail("Unexpected error: " + message);
95+
}
96+
97+
public void connectionEstablished(InputStream is, OutputStream os) {
98+
inputRef.set(is);
99+
outputRef.set(os);
100+
latch.countDown();
101+
}
102+
};
103+
104+
Socket.connect("example.com", 1234, connection);
105+
assertTrue(latch.await(2, TimeUnit.SECONDS));
106+
assertTrue(connection.isConnected());
107+
108+
state.enqueue("hi".getBytes(StandardCharsets.UTF_8));
109+
byte[] buffer = new byte[4];
110+
int read = inputRef.get().read(buffer);
111+
assertEquals(2, read);
112+
assertArrayEquals(new byte[]{'h', 'i'}, Arrays.copyOf(buffer, read));
113+
114+
outputRef.get().write(new byte[]{1, 2, 3});
115+
outputRef.get().flush();
116+
assertEquals(1, state.outbound.size());
117+
assertArrayEquals(new byte[]{1, 2, 3}, state.outbound.get(0));
118+
119+
outputRef.get().write(new byte[]{9, 8, 7}, 1, 2);
120+
outputRef.get().write(255);
121+
outputRef.get().flush();
122+
assertEquals(3, state.outbound.size());
123+
assertArrayEquals(new byte[]{8, 7}, state.outbound.get(1));
124+
assertArrayEquals(new byte[]{(byte) 255}, state.outbound.get(2));
125+
126+
outputRef.get().close();
127+
assertFalse(connection.isConnected());
128+
assertFalse(state.connected);
129+
130+
assertEquals(-1, inputRef.get().read(new byte[4]));
131+
}
132+
133+
@Test
134+
void connectWithCloseDisconnectsSocket() throws Exception {
135+
FakeSocketState state = prepareSocketState("close.me", 9000);
136+
CountDownLatch latch = new CountDownLatch(1);
137+
SocketConnection connection = new SocketConnection() {
138+
public void connectionError(int errorCode, String message) {
139+
fail();
140+
}
141+
142+
public void connectionEstablished(InputStream is, OutputStream os) {
143+
latch.countDown();
144+
}
145+
};
146+
147+
Socket.Close closeHandle = Socket.connectWithClose("close.me", 9000, connection);
148+
assertTrue(latch.await(2, TimeUnit.SECONDS));
149+
closeHandle.close();
150+
assertFalse(state.connected);
151+
}
152+
153+
@Test
154+
void inputStreamCloseDisconnectsSocket() throws Exception {
155+
FakeSocketState state = prepareSocketState("input", 1100);
156+
CountDownLatch latch = new CountDownLatch(1);
157+
AtomicReference<InputStream> inputRef = new AtomicReference<InputStream>();
158+
159+
SocketConnection connection = new SocketConnection() {
160+
public void connectionError(int errorCode, String message) {
161+
fail();
162+
}
163+
164+
public void connectionEstablished(InputStream is, OutputStream os) {
165+
inputRef.set(is);
166+
latch.countDown();
167+
}
168+
};
169+
170+
Socket.connect("input", 1100, connection);
171+
assertTrue(latch.await(2, TimeUnit.SECONDS));
172+
inputRef.get().close();
173+
assertFalse(state.connected);
174+
}
175+
176+
@Test
177+
void getHostOrIpDelegatesToImplementation() {
178+
when(implementation.getHostOrIP()).thenReturn("device.local");
179+
assertEquals("device.local", Socket.getHostOrIP());
180+
}
181+
182+
private FakeSocketState prepareSocketState(String host, int port) {
183+
FakeSocketState state = new FakeSocketState();
184+
when(implementation.isSocketAvailable()).thenReturn(true);
185+
when(implementation.connectSocket(eq(host), eq(port), anyInt())).thenReturn(state);
186+
when(implementation.isSocketConnected(state)).thenAnswer(invocation -> state.connected);
187+
when(implementation.getSocketAvailableInput(state)).thenAnswer(invocation -> state.available());
188+
when(implementation.readFromSocketStream(state)).thenAnswer(invocation -> state.read());
189+
when(implementation.getSocketErrorCode(state)).thenReturn(0);
190+
when(implementation.getSocketErrorMessage(state)).thenReturn(null);
191+
192+
doAnswer(invocation -> {
193+
state.write(invocation.getArgument(1));
194+
return null;
195+
}).when(implementation).writeToSocketStream(eq(state), any(byte[].class));
196+
197+
doAnswer(invocation -> {
198+
state.connected = false;
199+
return null;
200+
}).when(implementation).disconnectSocket(eq(state));
201+
202+
return state;
203+
}
204+
205+
private static class FakeSocketState {
206+
private final ConcurrentLinkedQueue<byte[]> inbound = new ConcurrentLinkedQueue<byte[]>();
207+
private final List<byte[]> outbound = new ArrayList<byte[]>();
208+
private volatile boolean connected = true;
209+
210+
void enqueue(byte[] data) {
211+
inbound.add(data);
212+
}
213+
214+
int available() {
215+
int total = 0;
216+
for (byte[] bytes : inbound) {
217+
total += bytes.length;
218+
}
219+
return total;
220+
}
221+
222+
byte[] read() {
223+
byte[] data = inbound.poll();
224+
if (data == null) {
225+
return new byte[0];
226+
}
227+
return data;
228+
}
229+
230+
void write(byte[] data) {
231+
outbound.add(Arrays.copyOf(data, data.length));
232+
}
233+
}
234+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.codename1.io;
2+
3+
import com.codename1.impl.CodenameOneImplementation;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.IOException;
8+
import java.util.Arrays;
9+
import java.util.Vector;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
14+
15+
class StorageTest {
16+
private CodenameOneImplementation implementation;
17+
private Storage storage;
18+
19+
@BeforeEach
20+
void setUp() {
21+
implementation = TestImplementationProvider.installImplementation(true);
22+
storage = Storage.getInstance();
23+
storage.clearStorage();
24+
storage.clearCache();
25+
storage.setNormalizeNames(true);
26+
}
27+
28+
@Test
29+
void writeObjectCachesAndPersistsEntries() {
30+
Vector<String> payload = new Vector<String>();
31+
payload.add("alpha");
32+
payload.add("beta");
33+
34+
assertTrue(storage.writeObject("vectorEntry", payload));
35+
assertTrue(storage.exists("vectorEntry"));
36+
37+
Object firstRead = storage.readObject("vectorEntry");
38+
assertEquals(payload, firstRead);
39+
assertSame(firstRead, storage.readObject("vectorEntry"));
40+
41+
storage.clearCache();
42+
Object secondRead = storage.readObject("vectorEntry");
43+
assertEquals(payload, secondRead);
44+
assertNotSame(firstRead, secondRead);
45+
}
46+
47+
@Test
48+
void createInputStreamThrowsWhenEntryMissing() {
49+
assertThrows(IOException.class, () -> storage.createInputStream("missing"));
50+
}
51+
52+
@Test
53+
void clearStoragePurgesEntriesAndCache() {
54+
storage.writeObject("transient", "value");
55+
assertNotNull(storage.readObject("transient"));
56+
57+
storage.clearStorage();
58+
59+
assertFalse(storage.exists("transient"));
60+
assertNull(storage.readObject("transient"));
61+
}
62+
63+
@Test
64+
void normalizedNamesAreUsedByDefault() {
65+
String originalKey = "dir/with:illegal*chars";
66+
storage.writeObject(originalKey, "data");
67+
68+
assertTrue(storage.exists(originalKey));
69+
assertTrue(Arrays.asList(storage.listEntries()).contains("dir_with_illegal_chars"));
70+
}
71+
72+
@Test
73+
void disablingNormalizationUsesRawKey() {
74+
storage.setNormalizeNames(false);
75+
String rawKey = "raw/name=kept";
76+
storage.writeObject(rawKey, "v");
77+
78+
assertTrue(Arrays.asList(storage.listEntries()).contains(rawKey));
79+
}
80+
81+
@Test
82+
void flushStorageCacheDelegatesToImplementation() {
83+
storage.flushStorageCache();
84+
verify(implementation).flushStorageCache();
85+
}
86+
87+
@Test
88+
void entrySizeReflectsStoredObjectSize() {
89+
String key = "sized";
90+
storage.writeObject(key, "payload");
91+
int size = storage.entrySize(key);
92+
assertTrue(size > 0);
93+
}
94+
95+
@Test
96+
void deleteStorageFileRemovesEntryAndCache() {
97+
String key = "toDelete";
98+
storage.writeObject(key, "data");
99+
assertNotNull(storage.readObject(key));
100+
101+
storage.deleteStorageFile(key);
102+
103+
assertFalse(storage.exists(key));
104+
assertNull(storage.readObject(key));
105+
}
106+
107+
@Test
108+
void existsDelegatesToImplementationWithNormalization() {
109+
String key = "needs?normalization";
110+
when(implementation.storageFileExists("needs_normalization")).thenReturn(true);
111+
assertTrue(storage.exists(key));
112+
}
113+
}

0 commit comments

Comments
 (0)