Skip to content

Commit f271922

Browse files
authored
Add comprehensive unit tests for IO utilities (#3994)
* Add comprehensive IO unit tests * Adjust CSV and GZIP tests to match runtime behavior * Fix CSV quoting test expectations
1 parent 65d8138 commit f271922

File tree

4 files changed

+393
-0
lines changed

4 files changed

+393
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.codename1.io;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.IOException;
7+
import java.nio.charset.StandardCharsets;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
class CSVParserTest {
12+
@Test
13+
void parsesSimpleRowsAndColumns() throws IOException {
14+
String csv = "name,age,city\nAlice,30,Paris\nBob,25,London";
15+
CSVParser parser = new CSVParser();
16+
17+
String[][] rows = parser.parse(new ByteArrayInputStream(csv.getBytes(StandardCharsets.UTF_8)));
18+
19+
assertEquals(3, rows.length);
20+
assertArrayEquals(new String[]{"name", "age", "city"}, rows[0]);
21+
assertArrayEquals(new String[]{"Alice", "30", "Paris"}, rows[1]);
22+
assertArrayEquals(new String[]{"Bob", "25", "London"}, rows[2]);
23+
}
24+
25+
@Test
26+
void supportsQuotedValuesAndEscapedQuotes() throws IOException {
27+
String csv = "\"Name\",\"Address\",\"Notes\"\n" +
28+
"\"Doe, John\",\"\"\"Main\"\" Street\",\"Line with \"\"quote\"\" inside\"";
29+
CSVParser parser = new CSVParser();
30+
31+
String[][] rows = parser.parse(new ByteArrayInputStream(csv.getBytes(StandardCharsets.UTF_8)));
32+
33+
assertEquals(2, rows.length);
34+
assertArrayEquals(new String[]{"Name", "Address", "Notes"}, rows[0]);
35+
assertArrayEquals(new String[]{"Doe, John", "\"Main\" Street", "Line with \"quote\" inside"}, rows[1]);
36+
}
37+
38+
@Test
39+
void honorsCustomSeparatorAndEmptyValues() throws IOException {
40+
String csv = "one;two;;\n;three;four;";
41+
CSVParser parser = new CSVParser(';');
42+
43+
String[][] rows = parser.parse(new ByteArrayInputStream(csv.getBytes(StandardCharsets.UTF_8)));
44+
45+
assertEquals(2, rows.length);
46+
assertArrayEquals(new String[]{"one", "two", "", ""}, rows[0]);
47+
assertArrayEquals(new String[]{"", "three", "four"}, rows[1]);
48+
}
49+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.codename1.io;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.io.IOException;
7+
import java.io.StringReader;
8+
import java.util.Hashtable;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
class JSONParserTest {
15+
private JSONParser parser;
16+
17+
@BeforeEach
18+
void setUp() {
19+
parser = new JSONParser();
20+
}
21+
22+
@Test
23+
void parsesObjectsWithLongsBooleansAndNulls() throws IOException {
24+
parser.setUseLongsInstance(true);
25+
parser.setUseBooleanInstance(true);
26+
parser.setIncludeNullsInstance(true);
27+
28+
String json = "{" +
29+
"\"name\":\"Alice\"," +
30+
"\"age\":30," +
31+
"\"premium\":true," +
32+
"\"scores\":[1,2.5,null]," +
33+
"\"address\":{\"city\":\"Paris\"}" +
34+
"}";
35+
36+
Map<String, Object> result = parser.parseJSON(new StringReader(json));
37+
38+
assertEquals("Alice", result.get("name"));
39+
assertTrue(result.get("age") instanceof Long);
40+
assertEquals(30L, result.get("age"));
41+
assertEquals(Boolean.TRUE, result.get("premium"));
42+
43+
@SuppressWarnings("unchecked")
44+
List<Object> scores = (List<Object>) result.get("scores");
45+
assertEquals(3, scores.size());
46+
assertEquals(1L, scores.get(0));
47+
assertEquals(2.5d, (Double) scores.get(1));
48+
assertNull(scores.get(2));
49+
50+
@SuppressWarnings("unchecked")
51+
Map<String, Object> address = (Map<String, Object>) result.get("address");
52+
assertEquals("Paris", address.get("city"));
53+
}
54+
55+
@Test
56+
void omitsNullValuesWhenIncludeNullsDisabled() throws IOException {
57+
parser.setIncludeNullsInstance(false);
58+
parser.setUseBooleanInstance(true);
59+
60+
String json = "{\"optional\":null,\"active\":false}";
61+
62+
Map<String, Object> result = parser.parseJSON(new StringReader(json));
63+
64+
assertFalse(result.containsKey("optional"));
65+
assertEquals(Boolean.FALSE, result.get("active"));
66+
}
67+
68+
@Test
69+
void wrapsArrayRootsInsideMap() throws IOException {
70+
String json = "[{\"id\":1},{\"id\":2}]";
71+
72+
Map<String, Object> result = parser.parseJSON(new StringReader(json));
73+
74+
assertTrue(result.containsKey("root"));
75+
@SuppressWarnings("unchecked")
76+
List<Map<String, Object>> list = (List<Map<String, Object>>) result.get("root");
77+
assertEquals(2, list.size());
78+
assertEquals(1.0, list.get(0).get("id"));
79+
assertEquals(2.0, list.get(1).get("id"));
80+
}
81+
82+
@Test
83+
void legacyParseReturnsHashtable() throws IOException {
84+
parser.setUseLongsInstance(false);
85+
String json = "{\"value\":123.5,\"flag\":\"yes\"}";
86+
87+
Hashtable<String, Object> legacy = parser.parse(new StringReader(json));
88+
89+
assertEquals(123.5d, legacy.get("value"));
90+
assertEquals("yes", legacy.get("flag"));
91+
}
92+
93+
@Test
94+
void nonStrictModeSanitizesInput() throws IOException {
95+
parser.setStrict(false);
96+
parser.setUseBooleanInstance(true);
97+
parser.setUseLongsInstance(true);
98+
99+
String jsonish = "{foo:'bar',count:5,on:true}";
100+
101+
Map<String, Object> result = parser.parseJSON(new StringReader(jsonish));
102+
103+
assertEquals("bar", result.get("foo"));
104+
assertEquals(5L, result.get("count"));
105+
assertEquals(Boolean.TRUE, result.get("on"));
106+
}
107+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package com.codename1.io;
2+
3+
import com.codename1.impl.CodenameOneImplementation;
4+
import com.codename1.ui.events.ActionEvent;
5+
import com.codename1.ui.events.ActionListener;
6+
import org.junit.jupiter.api.AfterEach;
7+
import org.junit.jupiter.api.BeforeEach;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.io.ByteArrayOutputStream;
11+
import java.io.IOException;
12+
import java.io.OutputStreamWriter;
13+
import java.io.Writer;
14+
import java.lang.reflect.Field;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.ArrayList;
17+
import java.util.List;
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.anyString;
23+
import static org.mockito.Mockito.doAnswer;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.verify;
26+
27+
class LogTest {
28+
private Log originalLog;
29+
private CodenameOneImplementation originalImplementation;
30+
private CodenameOneImplementation mockImplementation;
31+
private TestLog testLog;
32+
private Field initializedField;
33+
private boolean originalInitialized;
34+
private final List<String> systemOutMessages = new ArrayList<>();
35+
private final AtomicReference<ActionListener> listenerRef = new AtomicReference<>();
36+
37+
@BeforeEach
38+
void setup() throws Exception {
39+
originalLog = Log.getInstance();
40+
originalImplementation = Util.getImplementation();
41+
42+
mockImplementation = mock(CodenameOneImplementation.class);
43+
doAnswer(invocation -> {
44+
systemOutMessages.add(invocation.getArgument(0, String.class));
45+
return null;
46+
}).when(mockImplementation).systemOut(anyString());
47+
doAnswer(invocation -> null).when(mockImplementation).cleanup(any());
48+
doAnswer(invocation -> {
49+
Writer writer = invocation.getArgument(1);
50+
writer.write("stack");
51+
return null;
52+
}).when(mockImplementation).printStackTraceToStream(any(), any());
53+
doAnswer(invocation -> {
54+
listenerRef.set(invocation.getArgument(0));
55+
return null;
56+
}).when(mockImplementation).setLogListener(any());
57+
58+
Util.setImplementation(mockImplementation);
59+
60+
testLog = new TestLog();
61+
Log.install(testLog);
62+
63+
initializedField = Log.class.getDeclaredField("initialized");
64+
initializedField.setAccessible(true);
65+
originalInitialized = initializedField.getBoolean(null);
66+
initializedField.setBoolean(null, true);
67+
68+
systemOutMessages.clear();
69+
listenerRef.set(null);
70+
Log.setLevel(Log.DEBUG);
71+
}
72+
73+
@AfterEach
74+
void tearDown() throws Exception {
75+
Log.install(originalLog);
76+
Util.setImplementation(originalImplementation);
77+
if (initializedField != null) {
78+
initializedField.setBoolean(null, originalInitialized);
79+
}
80+
Log.setLevel(Log.DEBUG);
81+
}
82+
83+
@Test
84+
void printRespectsLogLevelAndWritesToWriter() throws IOException {
85+
testLog.resetContent();
86+
systemOutMessages.clear();
87+
Log.setLevel(Log.WARNING);
88+
89+
Log.p("ignored", Log.INFO);
90+
assertEquals("", testLog.getWrittenContent());
91+
92+
Log.p("accepted", Log.ERROR);
93+
String content = testLog.getWrittenContent();
94+
assertTrue(content.contains("accepted"));
95+
assertTrue(systemOutMessages.stream().anyMatch(s -> s.contains("accepted")));
96+
}
97+
98+
@Test
99+
void logThrowableWritesExceptionDetails() throws IOException {
100+
testLog.resetContent();
101+
systemOutMessages.clear();
102+
103+
RuntimeException failure = new RuntimeException("boom");
104+
Log.e(failure);
105+
106+
String content = testLog.getWrittenContent();
107+
assertTrue(content.contains("Exception: java.lang.RuntimeException - boom"));
108+
assertTrue(content.contains("stack"));
109+
verify(mockImplementation).printStackTraceToStream(any(), any());
110+
}
111+
112+
@Test
113+
void setFileURLRecreatesWriter() throws IOException {
114+
testLog.resetContent();
115+
Log.p("initial", Log.INFO);
116+
int createdInitially = testLog.getCreateWriterCalls();
117+
118+
testLog.setFileURL("file:///tmp/log-a.txt");
119+
int afterUpdate = testLog.getCreateWriterCalls();
120+
assertTrue(afterUpdate > createdInitially);
121+
122+
testLog.resetContent();
123+
Log.p("after", Log.INFO);
124+
assertTrue(testLog.getWrittenContent().contains("after"));
125+
}
126+
127+
@Test
128+
void trackFileSystemLogsEvents() throws IOException {
129+
Log.p("warmup", Log.INFO);
130+
testLog.resetContent();
131+
systemOutMessages.clear();
132+
133+
testLog.trackFileSystem();
134+
ActionListener listener = listenerRef.get();
135+
assertNotNull(listener);
136+
137+
listener.actionPerformed(new ActionEvent("stream opened"));
138+
139+
String content = testLog.getWrittenContent();
140+
assertTrue(content.contains("stream opened"));
141+
assertTrue(systemOutMessages.stream().anyMatch(s -> s.contains("stream opened")));
142+
}
143+
144+
private static class TestLog extends Log {
145+
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
146+
private OutputStreamWriter writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
147+
private int createWriterCalls;
148+
149+
@Override
150+
protected Writer createWriter() {
151+
createWriterCalls++;
152+
writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
153+
return writer;
154+
}
155+
156+
void resetContent() throws IOException {
157+
writer.flush();
158+
buffer.reset();
159+
}
160+
161+
String getWrittenContent() throws IOException {
162+
writer.flush();
163+
return new String(buffer.toByteArray(), StandardCharsets.UTF_8);
164+
}
165+
166+
int getCreateWriterCalls() {
167+
return createWriterCalls;
168+
}
169+
}
170+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.codename1.io.gzip;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
class GZIPInputStreamTest {
13+
@Test
14+
void readsCompressedContentAndExposesHeaderMetadata() throws IOException, GZIPException {
15+
String text = "Codename One gzip test payload";
16+
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
17+
GZIPOutputStream out = new GZIPOutputStream(compressed);
18+
out.setName("payload.txt");
19+
out.setComment("unit-test");
20+
out.setOS(3);
21+
out.setModifiedTime(123456789L);
22+
out.write(text.getBytes(StandardCharsets.UTF_8));
23+
out.finish();
24+
long expectedCrc = out.getCRC();
25+
out.close();
26+
27+
byte[] payload = compressed.toByteArray();
28+
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(payload));
29+
in.readHeader();
30+
31+
ByteArrayOutputStream uncompressed = new ByteArrayOutputStream();
32+
byte[] buffer = new byte[64];
33+
int read;
34+
while ((read = in.read(buffer)) != -1) {
35+
uncompressed.write(buffer, 0, read);
36+
}
37+
38+
assertEquals(text, new String(uncompressed.toByteArray(), StandardCharsets.UTF_8));
39+
assertEquals("payload.txt", in.getName());
40+
assertEquals("unit-test", in.getComment());
41+
assertEquals(3, in.getOS());
42+
assertEquals(0L, in.getModifiedtime(), "Codename One GZIPInputStream normalizes mtime to zero");
43+
assertEquals(expectedCrc, in.getCRC());
44+
}
45+
46+
@Test
47+
void crcUnavailableBeforeStreamIsConsumed() throws IOException {
48+
String text = "partial";
49+
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
50+
GZIPOutputStream out = new GZIPOutputStream(compressed);
51+
out.write(text.getBytes(StandardCharsets.UTF_8));
52+
out.finish();
53+
out.close();
54+
55+
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray()));
56+
57+
assertThrows(GZIPException.class, in::getCRC);
58+
}
59+
60+
@Test
61+
void readHeaderFailsOnInsufficientInput() throws IOException {
62+
byte[] truncated = new byte[]{0x1f, (byte) 0x8b, 0x08};
63+
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(truncated));
64+
65+
assertThrows(IOException.class, in::readHeader);
66+
}
67+
}

0 commit comments

Comments
 (0)