Overnight churnings
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.ecl.ECLConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLOIAPhase4Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLOIA oia;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
oia = new ECLOIA(screen, input, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInhibitionCodesAndStatusStrings() {
|
||||
screen.erase(false);
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.GetInputInhibited());
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.GetInputInhibitedType());
|
||||
assertEquals("READY", oia.GetStatusString());
|
||||
|
||||
// Test X-SYSTEM
|
||||
oia.SetInputInhibited(INHIBIT_SYSTEM_LOCK);
|
||||
assertTrue(oia.IsXSystem());
|
||||
assertTrue(oia.IsXWait());
|
||||
assertEquals("X-SYSTEM", oia.GetStatusString());
|
||||
|
||||
// Test X-NUM
|
||||
oia.SetInputInhibited(INHIBIT_NUMERIC_ONLY);
|
||||
assertTrue(oia.IsXNum());
|
||||
assertEquals("X-NUM", oia.GetStatusString());
|
||||
|
||||
// Test X-PROT
|
||||
oia.SetInputInhibited(INHIBIT_PROTECTED_FIELD);
|
||||
assertTrue(oia.IsXProt());
|
||||
assertEquals("X-PROT", oia.GetStatusString());
|
||||
|
||||
// Test X-OVERFLOW
|
||||
oia.SetInputInhibited(INHIBIT_OVERFLOW);
|
||||
assertTrue(oia.IsXOverflow());
|
||||
assertEquals("X-OVERFLOW", oia.GetStatusString());
|
||||
|
||||
// Test X-COMM
|
||||
oia.SetInputInhibited(INHIBIT_COMM_CHECK);
|
||||
assertTrue(oia.IsXComm());
|
||||
assertEquals("X-COMM", oia.GetStatusString());
|
||||
|
||||
// Test X-OP
|
||||
oia.SetInputInhibited(INHIBIT_OPERATOR_DUE);
|
||||
assertTrue(oia.IsXOperatorDue());
|
||||
assertEquals("X-OP", oia.GetStatusString());
|
||||
|
||||
// Test X-INSERT
|
||||
oia.SetInputInhibited(INHIBIT_NOT_INHIBITED);
|
||||
input.setInsertMode(true);
|
||||
assertTrue(oia.IsInsertMode());
|
||||
assertTrue(oia.isXInsert());
|
||||
assertEquals("X-INSERT", oia.GetStatusString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlphanumericTypeGetters() {
|
||||
screen.erase(false);
|
||||
assertEquals(TYPE_ALPHANUMERIC, oia.GetAlphanumericType());
|
||||
assertEquals("A", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsAlphanumeric());
|
||||
assertFalse(oia.IsNumeric());
|
||||
assertFalse(oia.IsDBCS());
|
||||
|
||||
// Numeric field
|
||||
screen.setFieldAttribute(0, (byte) (FA_PRINTABLE | FA_NUMERIC));
|
||||
screen.setCursorAddress(1);
|
||||
assertEquals(TYPE_NUMERIC, oia.GetAlphanumericType());
|
||||
assertEquals("N", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsNumeric());
|
||||
assertFalse(oia.IsAlphanumeric());
|
||||
|
||||
// DBCS cell
|
||||
ExtendedAttribute ea = screen.getCell(1);
|
||||
ea.cs = ExtendedAttribute.CS_DBCS;
|
||||
assertEquals(TYPE_DBCS, oia.GetAlphanumericType());
|
||||
assertEquals("D", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsDBCS());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSynchronizationPrimitives() {
|
||||
screen.erase(false);
|
||||
assertTrue(oia.WaitForInput(100));
|
||||
assertTrue(oia.WaitForSystemAvailable(100));
|
||||
assertTrue(oia.WaitForAppAvailable(100));
|
||||
|
||||
// Lock keyboard, wait should timeout
|
||||
input.setKeyboardLocked(true);
|
||||
assertFalse(oia.WaitForInput(50));
|
||||
input.setKeyboardLocked(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.ecl.ECLConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLPSPhase4Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLPS ps;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
ps = new ECLPS(screen, input, translator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchPSComprehensiveOverloads() {
|
||||
screen.erase(false);
|
||||
screen.setText("IBM Host On-Demand ECL Automation Presentation Space");
|
||||
|
||||
// 1-based indexing overloads
|
||||
assertEquals(5, ps.SearchPS("Host"));
|
||||
assertEquals(5, ps.searchPS("Host"));
|
||||
assertEquals(5, ps.SearchPS("Host", 1));
|
||||
assertEquals(5, ps.SearchPS("Host", 1, SEARCH_FORWARD));
|
||||
assertEquals(5, ps.SearchPS("Host", 1, 1));
|
||||
assertEquals(5, ps.SearchPS("host", 1, 1, SEARCH_FORWARD, true));
|
||||
assertEquals(0, ps.SearchPS("host", 1, 1, SEARCH_FORWARD, false));
|
||||
|
||||
// Bounded range searches
|
||||
assertEquals(5, ps.SearchPS("Host", 1, 1, 1, 30));
|
||||
assertEquals(0, ps.SearchPS("Host", 1, 10, 1, 30)); // starts after "Host"
|
||||
assertEquals(5, ps.SearchPS("host", 1, 1, 1, 30, DIR_FORWARD, true));
|
||||
|
||||
// SearchPSExt
|
||||
int pos1 = ps.SearchPSExt("ECL", 1, 50, SEARCH_FORWARD, false, false);
|
||||
assertEquals(20, pos1);
|
||||
|
||||
int pos2 = ps.SearchPSExt("ecl", 1, 1, 1, 50, DIR_FORWARD, true, false);
|
||||
assertEquals(20, pos2);
|
||||
|
||||
// Backward search
|
||||
screen.setChar(1, 10, 'Z');
|
||||
screen.setChar(1, 11, 'O');
|
||||
screen.setChar(1, 12, 'O');
|
||||
|
||||
int backFound = ps.SearchPSExt("ZOO", 200, 1, SEARCH_BACKWARD, false, false);
|
||||
assertEquals(91, backFound); // row 1 col 10 is 80 + 10 + 1 = 91
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRectangularCopyAndPaste() {
|
||||
screen.erase(false);
|
||||
// Fill a small grid across rows 0, 1, 2
|
||||
String r0 = "ROW0_DATA_TEST";
|
||||
for (int c = 0; c < r0.length(); c++) screen.setChar(0, c, r0.charAt(c));
|
||||
String r1 = "ROW1_DATA_TEST";
|
||||
for (int c = 0; c < r1.length(); c++) screen.setChar(1, c, r1.charAt(c));
|
||||
String r2 = "ROW2_DATA_TEST";
|
||||
for (int c = 0; c < r2.length(); c++) screen.setChar(2, c, r2.charAt(c));
|
||||
|
||||
// Copy rectangular region rows 0..2, cols 0..3
|
||||
String copied = ps.copyString(0, 0, 2, 3);
|
||||
assertEquals("ROW0\nROW1\nROW2", copied);
|
||||
|
||||
// Test inverted coordinate normalization
|
||||
String inverted = ps.CopyString(2, 3, 0, 0);
|
||||
assertEquals("ROW0\nROW1\nROW2", inverted);
|
||||
|
||||
// Paste rectangular text
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(160, (byte) FA_PRINTABLE);
|
||||
|
||||
int pasted = ps.PasteString("ABC\nDEF\nGHI", 0, 1);
|
||||
assertEquals(9, pasted);
|
||||
|
||||
assertEquals("ABC", ps.getString(1, 3));
|
||||
assertEquals("DEF", ps.getString(81, 3));
|
||||
assertEquals("GHI", ps.getString(161, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendCharactersWithMnemonicsAndEscaping() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(20, (byte) FA_PRINTABLE);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
// Test mnemonic [tab] and escaped bracket [[
|
||||
ps.sendCharacters("A[[B[tab]C", 1);
|
||||
|
||||
assertEquals('A', screen.getChar(0, 1));
|
||||
assertEquals('[', screen.getChar(0, 2));
|
||||
assertEquals('B', screen.getChar(0, 3));
|
||||
// After tab, at pos 21
|
||||
assertEquals('C', screen.getChar(0, 21));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistPassThroughMethods() {
|
||||
screen.erase(false);
|
||||
ps.SetEntryAssistDOCmode(true);
|
||||
ps.SetEntryAssistWordWrap(true);
|
||||
ps.SetEntryAssistStartColumn(4);
|
||||
ps.SetEntryAssistEndColumn(72);
|
||||
ps.SetEntryAssistTabStops(new int[]{ 8, 16, 24, 32 });
|
||||
|
||||
assertTrue(ps.IsEntryAssistDOCmode());
|
||||
assertTrue(ps.IsEntryAssistWordWrap());
|
||||
assertEquals(4, ps.GetEntryAssistStartColumn());
|
||||
assertEquals(72, ps.GetEntryAssistEndColumn());
|
||||
assertArrayEquals(new int[]{ 8, 16, 24, 32 }, ps.GetEntryAssistTabStops());
|
||||
|
||||
ps.setCursorPos(0, 0);
|
||||
ps.WordTab(true);
|
||||
assertEquals(8, ps.getCursorCol());
|
||||
|
||||
ps.WordTab(true);
|
||||
assertEquals(16, ps.getCursorCol());
|
||||
|
||||
ps.WordTab(false);
|
||||
assertEquals(8, ps.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldManagementAccessors() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(50, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
assertNotNull(ps.GetFieldList());
|
||||
assertEquals(2, ps.GetFieldList().GetFieldCount());
|
||||
|
||||
ECLField f0 = ps.GetFirstField();
|
||||
assertNotNull(f0);
|
||||
assertEquals(0, f0.GetStart());
|
||||
assertEquals(1, f0.GetDataStart());
|
||||
assertEquals(49, f0.GetEnd());
|
||||
assertEquals(49, f0.GetLength());
|
||||
assertFalse(f0.IsProtected());
|
||||
|
||||
ECLField f1 = ps.GetNextField(f0);
|
||||
assertNotNull(f1);
|
||||
assertEquals(50, f1.GetStart());
|
||||
assertTrue(f1.IsProtected());
|
||||
|
||||
assertEquals(f0.GetStart(), ps.GetPreviousField(f1).GetStart());
|
||||
assertEquals(f0.GetStart(), ps.GetField(25).GetStart());
|
||||
assertEquals(f1.GetStart(), ps.GetField(60).GetStart());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.awt.Point;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Verification test suite for Phase 3: Host Graphics (GOCA / 3179G / Programmed Symbols) (GDDM).
|
||||
* Covers items 3.1 through 3.6 of the PhasedUpdates specification.
|
||||
*/
|
||||
public class GocaPhase3Test {
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.1: GOCA Order 0x23 / 0x27 - Viewing Window (Clipping Viewport)
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.1: Viewing Window (0x23 / 0x27) clipping bounds and raster isolation")
|
||||
public void testItem3_1_ViewingWindowClipping() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
assertFalse(plane.isViewingWindowActive());
|
||||
|
||||
// Define Viewing Window: x in [-100, 100], y in [-100, 100]
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSVW_DEF); // 0x23
|
||||
out.write(0x08); // 8-byte payload
|
||||
out.write((byte) 0xFF); out.write((byte) 0x9C); // xMin = -100
|
||||
out.write((byte) 0xFF); out.write((byte) 0x9C); // yMin = -100
|
||||
out.write(0x00); out.write(100); // xMax = 100
|
||||
out.write(0x00); out.write(100); // yMax = 100
|
||||
|
||||
byte[] vwOrder = out.toByteArray();
|
||||
decoder.decodeGoca(vwOrder, 0, vwOrder.length);
|
||||
|
||||
assertTrue(plane.isViewingWindowActive());
|
||||
assertEquals(-100, plane.getViewingWindowXMin());
|
||||
assertEquals(-100, plane.getViewingWindowYMin());
|
||||
assertEquals(100, plane.getViewingWindowXMax());
|
||||
assertEquals(100, plane.getViewingWindowYMax());
|
||||
|
||||
// 1. Draw a line entirely outside viewing window: (-300, -150) to (-200, -150)
|
||||
ByteArrayOutputStream lineOutside = new ByteArrayOutputStream();
|
||||
lineOutside.write(GocaConstants.G_GSCOL); lineOutside.write(0x02); // Red
|
||||
lineOutside.write(GocaConstants.G_GLINE); lineOutside.write(0x08);
|
||||
lineOutside.write((byte) 0xFE); lineOutside.write((byte) 0xD4); // -300
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x6A); // -150
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x38); // -200
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x6A); // -150
|
||||
|
||||
byte[] lineOutBytes = lineOutside.toByteArray();
|
||||
decoder.decodeGoca(lineOutBytes, 0, lineOutBytes.length);
|
||||
|
||||
int pxClipped = plane.mapX(-300);
|
||||
int pyClipped = plane.mapY(-150);
|
||||
int pixelVal = plane.getRgbBuffer()[pyClipped * plane.getCanvasWidth() + pxClipped];
|
||||
assertEquals(0, pixelVal, "Pixel outside viewing window must be clipped (0)");
|
||||
|
||||
// 2. Draw a line inside viewing window: (0, 0) to (50, 50)
|
||||
ByteArrayOutputStream lineInside = new ByteArrayOutputStream();
|
||||
lineInside.write(GocaConstants.G_GSCOL); lineInside.write(0x04); // Green
|
||||
lineInside.write(GocaConstants.G_GLINE); lineInside.write(0x08);
|
||||
lineInside.write(0x00); lineInside.write(0); lineInside.write(0x00); lineInside.write(0);
|
||||
lineInside.write(0x00); lineInside.write(50); lineInside.write(0x00); lineInside.write(50);
|
||||
|
||||
byte[] lineInBytes = lineInside.toByteArray();
|
||||
decoder.decodeGoca(lineInBytes, 0, lineInBytes.length);
|
||||
|
||||
int pxIn = plane.mapX(0);
|
||||
int pyIn = plane.mapY(0);
|
||||
int pixelInVal = plane.getRgbBuffer()[pyIn * plane.getCanvasWidth() + pxIn];
|
||||
assertNotEquals(0, pixelInVal, "Pixel inside viewing window must be rendered");
|
||||
|
||||
// 3. Clear viewing window via zero-length payload
|
||||
ByteArrayOutputStream clearVw = new ByteArrayOutputStream();
|
||||
clearVw.write(GocaConstants.G_GSVW);
|
||||
clearVw.write(0x00);
|
||||
byte[] clearVwBytes = clearVw.toByteArray();
|
||||
decoder.decodeGoca(clearVwBytes, 0, clearVwBytes.length);
|
||||
|
||||
assertFalse(plane.isViewingWindowActive());
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.2: GOCA Order 0x22 - Segment Characteristics
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.2: Segment Characteristics (0x22) flags decoding")
|
||||
public void testItem3_2_SegmentCharacteristics() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// Chained (0x80) | Dynamic (0x40) | Visible (0x00) -> 0xC0
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0xC0});
|
||||
assertTrue(decoder.isSegChained());
|
||||
assertTrue(decoder.isSegDynamic());
|
||||
assertTrue(decoder.isSegVisible());
|
||||
|
||||
// Non-chained (0x00) | Static (0x00) | Invisible (0x20) -> 0x20
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0x20});
|
||||
assertFalse(decoder.isSegChained());
|
||||
assertFalse(decoder.isSegDynamic());
|
||||
assertFalse(decoder.isSegVisible());
|
||||
|
||||
// Unchained (0x00) | Dynamic (0x40) | Visible (0x00) -> 0x40
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0x40});
|
||||
assertFalse(decoder.isSegChained());
|
||||
assertTrue(decoder.isSegDynamic());
|
||||
assertTrue(decoder.isSegVisible());
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.3: GOCA Order 0x11 - Fractional Line Width Calculation
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.3: Fractional Line Width (0x11) fixed-point ratio and plane synchronization")
|
||||
public void testItem3_3_FractionalLineWidth() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. Fixed point: integer = 3, fraction = 128/256 (0.5) -> 3.5
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSFLW); // 0x11
|
||||
out.write(0x03); // Integer part
|
||||
out.write(0x80); // Fractional part (128 / 256 = 0.5)
|
||||
|
||||
byte[] flwBytes = out.toByteArray();
|
||||
decoder.decodeGoca(flwBytes, 0, flwBytes.length);
|
||||
|
||||
assertEquals(3.5, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(3.5, plane.getFractionalLineWidth(), 0.001);
|
||||
|
||||
// 2. Single byte operand: integer = 2, frac = 0 -> 2.0
|
||||
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
|
||||
out2.write(GocaConstants.G_GSFLW);
|
||||
out2.write(0x02);
|
||||
|
||||
byte[] flwBytes2 = out2.toByteArray();
|
||||
decoder.decodeGoca(flwBytes2, 0, flwBytes2.length);
|
||||
|
||||
assertEquals(2.0, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(2.0, plane.getFractionalLineWidth(), 0.001);
|
||||
|
||||
// 3. Reset defaults restores 1.0
|
||||
decoder.resetDefaults();
|
||||
assertEquals(1.0, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(1.0, plane.getFractionalLineWidth(), 0.001);
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.4: GCS Character Baseline Angle and Shear Vector Transforms
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.4: Character Baseline Angle (GSCA 0x34) and Shear (GSCR 0x35) transforms")
|
||||
public void testItem3_4_CharacterAngleAndShear() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. GSCA 0x34: Character Angle vector (ax=0, ay=10) -> 90 degrees vertical
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSCA);
|
||||
out.write(0x04);
|
||||
out.write(0x00); out.write(0); // ax = 0
|
||||
out.write(0x00); out.write(10); // ay = 10
|
||||
|
||||
// GSCR 0x35: Character Shear vector (sx=10, sy=10) -> 45 degrees slant
|
||||
out.write(GocaConstants.G_GSCR);
|
||||
out.write(0x04);
|
||||
out.write(0x00); out.write(10); // sx = 10
|
||||
out.write(0x00); out.write(10); // sy = 10
|
||||
|
||||
byte[] stream = out.toByteArray();
|
||||
decoder.decodeGoca(stream, 0, stream.length);
|
||||
|
||||
assertEquals(90.0, decoder.getCharAngle(), 0.01);
|
||||
assertEquals(45.0, decoder.getCharShear(), 0.01);
|
||||
|
||||
// 2. Draw stroked vector text with active angle and shear
|
||||
ByteArrayOutputStream textStream = new ByteArrayOutputStream();
|
||||
textStream.write(GocaConstants.G_GSCS); textStream.write(0xF8); // Vector font
|
||||
textStream.write(GocaConstants.G_GCHST); // Character String Absolute
|
||||
textStream.write(0x05); // 4 bytes coord + 1 byte text 'A' (EBCDIC 0xC1)
|
||||
textStream.write(0x00); textStream.write(50);
|
||||
textStream.write(0x00); textStream.write(50);
|
||||
textStream.write((byte) 0xC1); // EBCDIC 'A'
|
||||
|
||||
byte[] textBytes = textStream.toByteArray();
|
||||
decoder.decodeGoca(textBytes, 0, textBytes.length);
|
||||
|
||||
assertTrue(plane.hasContent(), "Vector text with angle/shear must render to GraphicsPlane");
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.5: Multi-Plane Programmed Symbols (RWS 4..7) & Multi-Color PS Cells
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.5: Multi-Plane Programmed Symbols (RWS 4..7) & Triple-Plane Composite Color Cells")
|
||||
public void testItem3_5_ProgrammedSymbolsTriplePlane() {
|
||||
ProgramSymbolManager psm = new ProgramSymbolManager(9, 16);
|
||||
|
||||
// 1. Single-plane set (RWS 2)
|
||||
byte[] loadpsSingle = new byte[4 + 18];
|
||||
loadpsSingle[0] = 0x01; // Format 1
|
||||
loadpsSingle[1] = 0x41; // LCID 0x41
|
||||
loadpsSingle[2] = 0x40; // Start codepoint
|
||||
loadpsSingle[3] = 0x02; // RWS 2
|
||||
for (int i = 0; i < 18; i++) loadpsSingle[4 + i] = (byte) 0x55;
|
||||
psm.loadps(loadpsSingle);
|
||||
|
||||
ProgramSymbolSet singleSet = psm.getSymbolSet(0x41);
|
||||
assertNotNull(singleSet);
|
||||
assertFalse(singleSet.isTriplePlane());
|
||||
|
||||
// 2. Triple-plane set (RWS 4, LCID 0x42) loaded with all 3 planes simultaneously (54 bytes)
|
||||
byte[] loadpsTriple = new byte[4 + 54];
|
||||
loadpsTriple[0] = 0x01; // Format 1
|
||||
loadpsTriple[1] = 0x42; // LCID 0x42
|
||||
loadpsTriple[2] = 0x40; // Start codepoint
|
||||
loadpsTriple[3] = 0x04; // RWS 4 (Triple Plane)
|
||||
// Red slice: plane 1 (0x01)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + i] = (byte) 0xFF;
|
||||
// Green slice: plane 2 (0x02)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + 18 + i] = (byte) 0xFF;
|
||||
// Blue slice: plane 4 (0x04)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + 36 + i] = (byte) 0xFF;
|
||||
|
||||
psm.loadps(loadpsTriple);
|
||||
|
||||
ProgramSymbolSet tripleSet = psm.getSymbolSet(0x42);
|
||||
assertNotNull(tripleSet);
|
||||
assertTrue(tripleSet.isTriplePlane());
|
||||
|
||||
ProgramSymbolSet.SymbolSlot slot = psm.getSymbol(0x42, 0x40);
|
||||
assertNotNull(slot);
|
||||
assertTrue(slot.isTriplePlane());
|
||||
|
||||
// Verify multi-color RGB mapping: composite plane (1|2|4 = 7 -> White)
|
||||
int[] rgb = slot.getRgbPixels(GocaConstants.GOCA_COLORS[0], 0);
|
||||
assertNotNull(rgb);
|
||||
assertEquals(9 * 16, rgb.length);
|
||||
assertEquals(0xFFFFFFFF, rgb[0]); // White pixel (all 3 planes set)
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.6: Pick Correlation for Graphical Segment Selection
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.6: Pick Correlation & 56-Byte Graphic Input Structured Field generation")
|
||||
public void testItem3_6_PickCorrelationAndGraphicInput() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. Define Segment 100: drawing a line from (-50, -50) to (50, 50)
|
||||
ByteArrayOutputStream segStream = new ByteArrayOutputStream();
|
||||
segStream.write(GocaConstants.G_BEGSEGM);
|
||||
segStream.write(0x0C);
|
||||
segStream.write(0x00); segStream.write(0x00); segStream.write(0x00); segStream.write(100); // Seg ID 100
|
||||
segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(0x00); segStream.write(0x00); segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(GocaConstants.G_GSCOL); segStream.write(0x06); // Yellow
|
||||
segStream.write(GocaConstants.G_GLINE); segStream.write(0x08);
|
||||
segStream.write((byte) 0xFF); segStream.write((byte) 0xCE); // -50
|
||||
segStream.write((byte) 0xFF); segStream.write((byte) 0xCE); // -50
|
||||
segStream.write(0x00); segStream.write(50);
|
||||
segStream.write(0x00); segStream.write(50);
|
||||
segStream.write(GocaConstants.G_ENDSEGM); segStream.write(0x00);
|
||||
|
||||
byte[] segBytes = segStream.toByteArray();
|
||||
decoder.decodeGoca(segBytes, 0, segBytes.length);
|
||||
|
||||
// Hit test inside segment bounds: (0, 0)
|
||||
int pickedSeg = decoder.findPickedSegment(0, 0);
|
||||
assertEquals(100, pickedSeg, "Segment 100 should be hit at (0, 0)");
|
||||
|
||||
// Hit test outside segment bounds: (200, 200)
|
||||
int pickedMiss = decoder.findPickedSegment(200, 200);
|
||||
assertEquals(0, pickedMiss, "Missed click should default to segId 0");
|
||||
|
||||
// 2. Build 56-byte Graphic Input Structured Field via GraphicInputBuilder
|
||||
byte[] inputSF = GraphicInputBuilder.buildPickCorrelation(25, -25, 5);
|
||||
assertEquals(56, inputSF.length);
|
||||
// Header check: 0x0034 (52 byte payload length per IBM HOD)
|
||||
assertEquals(0x00, inputSF[0]);
|
||||
assertEquals(0x34, inputSF[1]);
|
||||
assertEquals(0x0F, inputSF[2]);
|
||||
assertEquals(0x0F, inputSF[3]);
|
||||
|
||||
// Coordinate checks: X = 25, Y = -25
|
||||
int gX = ((inputSF[24] & 0xFF) << 8) | (inputSF[25] & 0xFF);
|
||||
int gY = (short) (((inputSF[26] & 0xFF) << 8) | (inputSF[27] & 0xFF));
|
||||
assertEquals(25, gX);
|
||||
assertEquals(-25, gY);
|
||||
|
||||
// Mouse action flags: trigger class = 4, correlation class = 4, Button 1 = 0x01
|
||||
assertEquals(0x04, inputSF[31]);
|
||||
assertEquals(0x04, inputSF[33]);
|
||||
assertEquals(0x01, inputSF[35]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package haus.nightmare.lib3270j.screen;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.AbstractDBCSCodePage;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ScreenBufferPhase4Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistDOCModeAndWordWrap() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
screen.setEntryAssistStartColumn(0);
|
||||
screen.setEntryAssistEndColumn(20);
|
||||
|
||||
assertTrue(screen.isEntryAssistDOCmode());
|
||||
assertTrue(screen.isEntryAssistWordWrap());
|
||||
assertEquals(0, screen.getEntryAssistStartColumn());
|
||||
assertEquals(20, screen.getEntryAssistEndColumn());
|
||||
|
||||
// Type "HELLO WORLD " near column 20
|
||||
screen.setCursorPosition(0, 10);
|
||||
String s1 = "HELLO ";
|
||||
for (int i = 0; i < s1.length(); i++) {
|
||||
input.typeCharacter(s1.charAt(i));
|
||||
}
|
||||
assertEquals(16, screen.getCursorCol());
|
||||
assertEquals(0, screen.getCursorRow());
|
||||
|
||||
// Now type "TESTING" which starts at col 16 and crosses col 20
|
||||
String s2 = "TESTING";
|
||||
for (int i = 0; i < s2.length(); i++) {
|
||||
input.typeCharacter(s2.charAt(i));
|
||||
}
|
||||
|
||||
// "HELLO " should remain on row 0 (cols 10..15)
|
||||
assertEquals("HELLO ", screen.getString(10, 6));
|
||||
// "TESTING" should be soft-wrapped to row 1 (cols 0..6)
|
||||
assertEquals("TESTING", screen.getString(80, 7));
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertEquals(7, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistWordWrapTypingSpaceAtEndMargin() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
screen.setEntryAssistStartColumn(5);
|
||||
screen.setEntryAssistEndColumn(20);
|
||||
|
||||
screen.setCursorPosition(0, 20);
|
||||
input.typeCharacter(' ');
|
||||
|
||||
// Cursor should wrap to next line at startCol 5
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertEquals(5, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistTabStopsAndNavigation() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistTabStops(new int[]{ 10, 25, 50, 70 });
|
||||
assertArrayEquals(new int[]{ 10, 25, 50, 70 }, screen.getEntryAssistTabStops());
|
||||
|
||||
screen.setCursorPosition(0, 0);
|
||||
screen.processWordTab(true);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(true);
|
||||
assertEquals(25, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(true);
|
||||
assertEquals(50, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(false);
|
||||
assertEquals(25, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(false);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessDeleteWord() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(40, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
// Field contains "ALPHA BRAVO CHARLIE"
|
||||
String text = "ALPHA BRAVO CHARLIE";
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
screen.setChar(0, 1 + i, text.charAt(i));
|
||||
}
|
||||
|
||||
// Place cursor at "BRAVO" (col 7)
|
||||
screen.setCursorPosition(0, 7);
|
||||
screen.processDeleteWord();
|
||||
|
||||
// "BRAVO " should be deleted, leaving "ALPHA CHARLIE"
|
||||
assertEquals("ALPHA CHARLIE", screen.getString(1, 13));
|
||||
assertTrue(screen.isModified(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDBCSSOSIPairBalancing() {
|
||||
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) dbcsTranslator.getCodePage();
|
||||
cp.registerDbcsPair(0x4341, '\u6771');
|
||||
|
||||
ScreenBuffer dbcsScreen = new ScreenBuffer(TerminalModel.IBM_3279_2, dbcsTranslator);
|
||||
dbcsScreen.erase(false);
|
||||
dbcsScreen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
dbcsScreen.setFieldAttribute(20, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
// Insert DBCS character
|
||||
boolean inserted = dbcsScreen.insertChar(1, '\u6771');
|
||||
assertTrue(inserted);
|
||||
assertEquals(ExtendedAttribute.CS_DBCS, dbcsScreen.getCell(1).cs);
|
||||
assertEquals(ExtendedAttribute.DB_LEFT, dbcsScreen.getCell(1).db);
|
||||
assertEquals(ExtendedAttribute.DB_RIGHT, dbcsScreen.getCell(2).db);
|
||||
|
||||
// Delete DBCS character at left half
|
||||
boolean deleted = dbcsScreen.deleteChar(1);
|
||||
assertTrue(deleted);
|
||||
assertEquals(0, dbcsScreen.getCell(1).ec);
|
||||
assertEquals(0, dbcsScreen.getCell(2).ec);
|
||||
|
||||
// Test balanceSOSI removes adjacent empty SO SI
|
||||
dbcsScreen.setCell(3, 0x0E); // SO
|
||||
dbcsScreen.setCell(4, 0x0F); // SI
|
||||
dbcsScreen.balanceSOSI();
|
||||
assertEquals(0, dbcsScreen.getCell(3).ec);
|
||||
assertEquals(0, dbcsScreen.getCell(4).ec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrappedFieldListHandling() {
|
||||
screen.erase(false);
|
||||
// Field 1 at pos 100..499
|
||||
screen.setFieldAttribute(100, (byte) FA_PRINTABLE);
|
||||
// Field 2 at pos 500..99 (wrapping around end of buffer)
|
||||
screen.setFieldAttribute(500, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
ECLFieldList list = screen.buildFieldList();
|
||||
assertEquals(2, list.getFieldCount());
|
||||
|
||||
ECLField f1 = list.getFirstField();
|
||||
assertNotNull(f1);
|
||||
assertEquals(100, f1.getStart());
|
||||
assertEquals(101, f1.getDataStart());
|
||||
assertEquals(499, f1.getEnd());
|
||||
assertEquals(399, f1.getLength());
|
||||
assertFalse(f1.isWrapped());
|
||||
assertFalse(f1.isProtected());
|
||||
|
||||
ECLField f2 = list.getNextField(f1);
|
||||
assertNotNull(f2);
|
||||
assertEquals(500, f2.getStart());
|
||||
assertEquals(501, f2.getDataStart());
|
||||
assertEquals(99, f2.getEnd());
|
||||
assertEquals(1920 - 500 - 1 + 100, f2.getLength());
|
||||
assertTrue(f2.isWrapped());
|
||||
assertTrue(f2.isProtected());
|
||||
|
||||
// Test containment across wrap
|
||||
assertTrue(f2.contains(500));
|
||||
assertTrue(f2.contains(501));
|
||||
assertTrue(f2.contains(1919));
|
||||
assertTrue(f2.contains(0));
|
||||
assertTrue(f2.contains(99));
|
||||
assertFalse(f2.contains(100));
|
||||
assertFalse(f2.contains(200));
|
||||
|
||||
// Test field lookup by position
|
||||
assertEquals(f2.getStart(), list.findField(50).getStart());
|
||||
assertEquals(f1.getStart(), list.findField(200).getStart());
|
||||
assertEquals(f2.getStart(), list.findField(600).getStart());
|
||||
|
||||
// Test navigation
|
||||
assertEquals(f2.getStart(), list.getPreviousField(f1).getStart());
|
||||
assertNull(list.getNextField(f2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleFieldFullBufferWrap() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(250, (byte) FA_PRINTABLE);
|
||||
|
||||
ECLFieldList list = screen.buildFieldList();
|
||||
assertEquals(1, list.getFieldCount());
|
||||
|
||||
ECLField f = list.getFirstField();
|
||||
assertNotNull(f);
|
||||
assertEquals(250, f.getStart());
|
||||
assertEquals(251, f.getDataStart());
|
||||
assertEquals(249, f.getEnd());
|
||||
assertEquals(1919, f.getLength());
|
||||
assertTrue(f.isWrapped());
|
||||
|
||||
assertTrue(f.contains(0));
|
||||
assertTrue(f.contains(250));
|
||||
assertTrue(f.contains(1919));
|
||||
}
|
||||
}
|
||||
@@ -241,4 +241,51 @@ public class ProxyConnectionTest {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemPropertiesProxyFallback() throws Exception {
|
||||
try (ServerSocket proxyServer = new ServerSocket(0)) {
|
||||
int proxyPort = proxyServer.getLocalPort();
|
||||
CountDownLatch serverHandshakeDone = new CountDownLatch(1);
|
||||
|
||||
Thread serverThread = new Thread(() -> {
|
||||
try (Socket clientSock = proxyServer.accept()) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSock.getInputStream(), StandardCharsets.US_ASCII));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) break;
|
||||
}
|
||||
OutputStream out = clientSock.getOutputStream();
|
||||
out.write("HTTP/1.1 200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII));
|
||||
out.flush();
|
||||
serverHandshakeDone.countDown();
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
serverThread.setDaemon(true);
|
||||
serverThread.start();
|
||||
|
||||
String origHost = System.getProperty("http.proxyHost");
|
||||
String origPort = System.getProperty("http.proxyPort");
|
||||
try {
|
||||
System.setProperty("http.proxyHost", "127.0.0.1");
|
||||
System.setProperty("http.proxyPort", String.valueOf(proxyPort));
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig("test.fallback.org", 23);
|
||||
config.setConnectTimeoutMs(5000);
|
||||
|
||||
TelnetFSM fsm = new TelnetFSM(config, new haus.nightmare.lib3270j.screen.ScreenBuffer(TerminalModel.IBM_3279_4, new haus.nightmare.lib3270j.charset.EbcdicTranslator()), null);
|
||||
TelnetConnection connection = new TelnetConnection(config, fsm);
|
||||
|
||||
connection.connect();
|
||||
assertTrue(connection.isConnected());
|
||||
assertTrue(serverHandshakeDone.await(3, TimeUnit.SECONDS));
|
||||
connection.disconnect();
|
||||
} finally {
|
||||
if (origHost != null) System.setProperty("http.proxyHost", origHost);
|
||||
else System.clearProperty("http.proxyHost");
|
||||
if (origPort != null) System.setProperty("http.proxyPort", origPort);
|
||||
else System.clearProperty("http.proxyPort");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user