Add bugfixes from discord
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 42s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m9s
Release j3270 / Build & Publish Release (push) Successful in 32s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 42s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 42s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m9s
Release j3270 / Build & Publish Release (push) Successful in 32s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 42s
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package haus.nightmare.lib3270j.datastream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for high-throughput zero-allocation buffer primitives.
|
||||
*/
|
||||
public class FastByteBufferTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ReusableByteBufferPool.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("FastByteBuffer operations: write, grow, reset, slice, and direct array access")
|
||||
public void testFastByteBufferBasicOperations() {
|
||||
FastByteBuffer buf = new FastByteBuffer(16);
|
||||
assertEquals(0, buf.size());
|
||||
assertTrue(buf.buffer().length >= 16);
|
||||
|
||||
buf.write(0x11);
|
||||
buf.write(0x22);
|
||||
assertEquals(2, buf.size());
|
||||
assertEquals((byte) 0x11, buf.buffer()[0]);
|
||||
assertEquals((byte) 0x22, buf.buffer()[1]);
|
||||
|
||||
byte[] payload = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
buf.write(payload, 0, payload.length);
|
||||
assertEquals(7, buf.size());
|
||||
|
||||
buf.write(payload, 1, 3); // write 0x02, 0x03, 0x04
|
||||
assertEquals(10, buf.size());
|
||||
|
||||
// Test auto-growth
|
||||
byte[] largeData = new byte[100];
|
||||
for (int i = 0; i < largeData.length; i++) {
|
||||
largeData[i] = (byte) (i & 0xFF);
|
||||
}
|
||||
buf.write(largeData, 0, largeData.length);
|
||||
assertEquals(110, buf.size());
|
||||
assertTrue(buf.buffer().length >= 110);
|
||||
|
||||
// Verify direct array access
|
||||
byte[] raw = buf.buffer();
|
||||
assertNotNull(raw);
|
||||
assertEquals((byte) 0x11, raw[0]);
|
||||
assertEquals((byte) 0x22, raw[1]);
|
||||
|
||||
// Verify ByteBuffer view
|
||||
ByteBuffer readOnly = buf.asByteBuffer();
|
||||
assertEquals(110, readOnly.remaining());
|
||||
assertEquals((byte) 0x11, readOnly.get());
|
||||
|
||||
// Verify ByteBuffer slice
|
||||
ByteBuffer slice = buf.slice(2, 5);
|
||||
assertEquals(5, slice.remaining());
|
||||
assertEquals((byte) 0x01, slice.get(0));
|
||||
assertEquals((byte) 0x05, slice.get(4));
|
||||
|
||||
// Verify copied array
|
||||
byte[] copied = buf.toByteArray();
|
||||
assertEquals(110, copied.length);
|
||||
assertEquals((byte) 0x11, copied[0]);
|
||||
|
||||
// Test reset
|
||||
buf.reset();
|
||||
assertEquals(0, buf.size());
|
||||
// Buffer retained for zero-allocation reuse
|
||||
assertTrue(buf.buffer().length >= 110);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ReusableByteBufferPool acquires, releases, and recycles tiered buffers")
|
||||
public void testByteBufferPoolRecycling() {
|
||||
byte[] small1 = ReusableByteBufferPool.acquire(256);
|
||||
assertEquals(ReusableByteBufferPool.SIZE_SMALL, small1.length);
|
||||
|
||||
byte[] medium1 = ReusableByteBufferPool.acquire(ReusableByteBufferPool.SIZE_MEDIUM);
|
||||
assertEquals(ReusableByteBufferPool.SIZE_MEDIUM, medium1.length);
|
||||
|
||||
byte[] large1 = ReusableByteBufferPool.acquire(ReusableByteBufferPool.SIZE_LARGE);
|
||||
assertEquals(ReusableByteBufferPool.SIZE_LARGE, large1.length);
|
||||
|
||||
// Non-standard oversized buffer
|
||||
byte[] huge = ReusableByteBufferPool.acquire(65536);
|
||||
assertEquals(65536, huge.length);
|
||||
|
||||
// Release back to pool
|
||||
ReusableByteBufferPool.release(small1);
|
||||
ReusableByteBufferPool.release(medium1);
|
||||
ReusableByteBufferPool.release(large1);
|
||||
ReusableByteBufferPool.release(huge);
|
||||
|
||||
// Next acquire should reuse the released instances
|
||||
byte[] small2 = ReusableByteBufferPool.acquire(128);
|
||||
assertSame(small1, small2, "Small buffer should be recycled from pool");
|
||||
|
||||
byte[] medium2 = ReusableByteBufferPool.acquire(2048);
|
||||
assertSame(medium1, medium2, "Medium buffer should be recycled from pool");
|
||||
|
||||
byte[] large2 = ReusableByteBufferPool.acquire(30000);
|
||||
assertSame(large1, large2, "Large buffer should be recycled from pool");
|
||||
|
||||
ByteBuffer bb = ReusableByteBufferPool.acquireByteBuffer(ReusableByteBufferPool.SIZE_SMALL);
|
||||
assertNotNull(bb);
|
||||
assertEquals(ReusableByteBufferPool.SIZE_SMALL, bb.capacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ReusableByteBufferPool is safe under concurrent acquisition and release")
|
||||
public void testConcurrentPoolAccess() throws InterruptedException {
|
||||
int threads = 8;
|
||||
int iterations = 1000;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threads);
|
||||
CountDownLatch latch = new CountDownLatch(threads);
|
||||
AtomicInteger failures = new AtomicInteger(0);
|
||||
|
||||
for (int t = 0; t < threads; t++) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
byte[] buf = ReusableByteBufferPool.acquire(ReusableByteBufferPool.SIZE_SMALL);
|
||||
if (buf == null || buf.length != ReusableByteBufferPool.SIZE_SMALL) {
|
||||
failures.incrementAndGet();
|
||||
}
|
||||
buf[0] = (byte) 0xAA;
|
||||
buf[1] = (byte) 0xBB;
|
||||
ReusableByteBufferPool.release(buf);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failures.incrementAndGet();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
executor.shutdown();
|
||||
assertEquals(0, failures.get(), "No failures occurred during concurrent pool operations");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package haus.nightmare.lib3270j.epi;
|
||||
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class EpiDataStream3270Test {
|
||||
|
||||
private Screen screen;
|
||||
private DataStream3270 dataStream;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
screen = new Screen(80, 24);
|
||||
dataStream = new DataStream3270(screen);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeAndDecodeAddress() {
|
||||
int[] testAddresses = {0, 1, 79, 80, 1919, 2000, 4095};
|
||||
byte[] target = new byte[2];
|
||||
|
||||
for (int addr : testAddresses) {
|
||||
dataStream.encodeAddress(target, 0, addr);
|
||||
int decoded = dataStream.decodeAddress(target[0], target[1]);
|
||||
assertEquals(addr, decoded, "Address round-trip mismatch for " + addr);
|
||||
}
|
||||
|
||||
// Test invalid decode
|
||||
assertEquals(-1, dataStream.decodeAddress((byte) 0x00, (byte) 0x00));
|
||||
assertEquals(-1, dataStream.decodeAddress((byte) 0xFF, (byte) 0xFF));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharacterTranslations() {
|
||||
char ebcdicChar = dataStream.toEbcdic('A'); // ASCII 65 -> EBCDIC 0xC1
|
||||
assertEquals('\u00c1', ebcdicChar);
|
||||
byte asciiByte = dataStream.toAscii(ebcdicChar); // EBCDIC 0xC1 -> ASCII 65
|
||||
assertEquals('A', (char) asciiByte);
|
||||
|
||||
// Boundary cases
|
||||
char low = dataStream.toEbcdic(0x10);
|
||||
assertEquals((char) 0x10, low);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnalyzeWriteAndFormat() throws Exception {
|
||||
// Build 3270 Write buffer:
|
||||
// CMD_W (49 / 0x31), WCC (0xC3), SBA (17), Addr(0, 0), SF (29), Attr (0xC1 = MDT set), Text "TEST"
|
||||
byte[] stream = new byte[11];
|
||||
stream[0] = 49; // Write
|
||||
stream[1] = (byte) 0xC3; // WCC
|
||||
stream[2] = 17; // SBA
|
||||
dataStream.encodeAddress(stream, 3, 0);
|
||||
stream[5] = 29; // SF
|
||||
stream[6] = (byte) 0xC1; // Attribute (MDT=1)
|
||||
stream[7] = (byte) 'T';
|
||||
stream[8] = (byte) 'E';
|
||||
stream[9] = (byte) 'S';
|
||||
stream[10] = (byte) 'T';
|
||||
|
||||
dataStream.analyze(stream, stream.length);
|
||||
|
||||
assertEquals(1, screen.fieldCount());
|
||||
Field f = screen.field(1);
|
||||
assertNotNull(f);
|
||||
assertTrue(f.hasAttribute());
|
||||
assertEquals(1, f.dataTag());
|
||||
|
||||
// Now format outbound buffer
|
||||
screen.setAID(AID.enter);
|
||||
screen.setCursor(1, 1);
|
||||
byte[] outBuf = new byte[100];
|
||||
int outLen = dataStream.format(outBuf);
|
||||
|
||||
assertTrue(outLen > 0);
|
||||
assertEquals(AID.enter.translate(), outBuf[0]);
|
||||
// Cursor addr at [1, 2]
|
||||
// SBA (17) at [3]
|
||||
assertEquals(17, outBuf[3]);
|
||||
// Data text starting at [6]
|
||||
assertEquals((byte) 'T', outBuf[6]);
|
||||
assertEquals((byte) 'E', outBuf[7]);
|
||||
assertEquals((byte) 'S', outBuf[8]);
|
||||
assertEquals((byte) 'T', outBuf[9]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBufferSerialization() throws Exception {
|
||||
// Erase/Write (53), WCC (0), SBA (17), Addr(0), SF (29), Attr(0xC0), Text "OK"
|
||||
byte[] stream = new byte[9];
|
||||
stream[0] = 53; // Erase / Write
|
||||
stream[1] = 0; // WCC
|
||||
stream[2] = 17; // SBA
|
||||
dataStream.encodeAddress(stream, 3, 0);
|
||||
stream[5] = 29; // SF
|
||||
stream[6] = (byte) 0xC0; // Unmodified attribute
|
||||
stream[7] = (byte) 'O';
|
||||
stream[8] = (byte) 'K';
|
||||
|
||||
dataStream.analyze(stream, stream.length);
|
||||
assertEquals(1, screen.fieldCount());
|
||||
|
||||
byte[] outBuf = new byte[100];
|
||||
int len = dataStream.readBuffer(outBuf);
|
||||
assertTrue(len >= 8);
|
||||
assertEquals(screen.getAID().translate(), outBuf[0]);
|
||||
assertEquals(17, outBuf[3]); // SBA
|
||||
assertEquals(29, outBuf[6]); // SF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEpiScreenBufferBridge() {
|
||||
screen.setWidth(80);
|
||||
screen.setDepth(24);
|
||||
screen.setCursor(2, 5);
|
||||
|
||||
Field f = new Field(screen, 80);
|
||||
f.setAttribute(true);
|
||||
f.setBaseAttribute((char) 0xC8);
|
||||
f.setBytes(0, new byte[]{(byte) 0xC1, (byte) 0xC2}, 2); // 'A', 'B'
|
||||
screen.insertField(f);
|
||||
|
||||
ScreenBuffer sb = new ScreenBuffer();
|
||||
EpiScreenBufferBridge.copyToScreenBuffer(screen, sb);
|
||||
|
||||
assertEquals(80, sb.getCols());
|
||||
assertEquals(24, sb.getRows());
|
||||
assertEquals(84, sb.getCursorAddress()); // row 2 (index 1) * 80 + col 5 (index 4) = 84
|
||||
|
||||
// Reverse copy
|
||||
Screen backScreen = new Screen();
|
||||
EpiScreenBufferBridge.copyFromScreenBuffer(sb, backScreen);
|
||||
assertEquals(80, backScreen.getWidth());
|
||||
assertEquals(24, backScreen.getDepth());
|
||||
assertEquals(2, backScreen.getCursorRow());
|
||||
assertEquals(5, backScreen.getCursorColumn());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user