Add facade classes for 3rd party programs
This commit is contained in:
@@ -56,4 +56,51 @@ public class ECLOIATest {
|
||||
input.setInsertMode(true);
|
||||
assertTrue(oia.isInsertMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXSystemAndXProtTransitionsAfterReset() {
|
||||
input.setOIA(oia);
|
||||
|
||||
// 1. Initial ready state
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
assertFalse(oia.isXSystem());
|
||||
assertFalse(oia.isXProt());
|
||||
|
||||
// 2. Clear / reset called explicitly (as on connect or packet receipt)
|
||||
oia.setInputInhibited(INHIBIT_NOT_INHIBITED);
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
assertFalse(oia.isXSystem());
|
||||
|
||||
// 3. User sends AID / locks keyboard -> MUST trigger X SYSTEM
|
||||
input.setKeyboardLocked(true);
|
||||
assertTrue(oia.isXSystem(), "X SYSTEM must be triggered when keyboard is locked");
|
||||
assertEquals(INHIBIT_SYSTEM_LOCK, oia.getInputInhibited());
|
||||
assertEquals("X-SYSTEM", oia.getStatusString());
|
||||
|
||||
// 4. Host unlocks keyboard
|
||||
input.setKeyboardLocked(false);
|
||||
assertFalse(oia.isXSystem());
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
assertEquals("READY", oia.getStatusString());
|
||||
|
||||
// 5. User types in protected field -> triggers X PROT
|
||||
oia.setInputInhibited(INHIBIT_PROTECTED_FIELD);
|
||||
assertTrue(oia.isXProt(), "X PROT must be triggered");
|
||||
assertFalse(oia.isXSystem());
|
||||
assertEquals(INHIBIT_PROTECTED_FIELD, oia.getInputInhibited());
|
||||
assertEquals("X-PROT", oia.getStatusString());
|
||||
|
||||
// 6. User presses Reset
|
||||
input.reset();
|
||||
assertFalse(input.isKeyboardLocked());
|
||||
assertFalse(oia.isXProt());
|
||||
assertFalse(oia.isXSystem());
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
|
||||
// 7. Subsequent AID send -> MUST trigger X SYSTEM again
|
||||
input.setKeyboardLocked(true);
|
||||
assertTrue(oia.isXSystem(), "X SYSTEM must trigger on subsequent lock after reset");
|
||||
assertEquals(INHIBIT_SYSTEM_LOCK, oia.getInputInhibited());
|
||||
assertEquals("X-SYSTEM", oia.getStatusString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import com.ibm.eNetwork.ECL.ECLConstants;
|
||||
import com.ibm.eNetwork.ECL.ECLErr;
|
||||
import com.ibm.eNetwork.ECL.ECLHostPrintSession;
|
||||
import com.ibm.eNetwork.ECL.ECLSession;
|
||||
import com.ibm.eNetwork.ECL.ECLPS;
|
||||
import com.ibm.eNetwork.ECL.ECLOIA;
|
||||
import com.ibm.eNetwork.ECL.ECLConnection;
|
||||
import com.ibm.eNetwork.ECL.ECLField;
|
||||
import com.ibm.eNetwork.ECL.ECLFieldList;
|
||||
import com.ibm.eNetwork.ECL.ECLScreenDesc;
|
||||
import com.ibm.eNetwork.ECL.ECLScreenReco;
|
||||
import com.ibm.eNetwork.ECL.ECLXfer;
|
||||
import com.ibm.eNetwork.ECL.event.ECLPSEvent;
|
||||
import com.ibm.eNetwork.ECL.event.ECLPSListener;
|
||||
import com.ibm.eNetwork.ECL.ECLOIANotify;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Targeted Unit Tests for Phase 22: HACL com.ibm.eNetwork.ECL.* Drop-In API Facades.
|
||||
*/
|
||||
public class Phase22HaclFacadesTest {
|
||||
|
||||
private ECLSession session;
|
||||
private ECLPS ps;
|
||||
private ECLOIA oia;
|
||||
private ECLConnection conn;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws ECLErr {
|
||||
Properties props = new Properties();
|
||||
props.setProperty(ECLSession.SESSION_HOST, "mainframe.test.local");
|
||||
props.setProperty(ECLSession.SESSION_PORT, "23");
|
||||
props.setProperty(ECLSession.SESSION_CODE_PAGE, "037");
|
||||
session = new ECLSession(props);
|
||||
ps = session.GetPS();
|
||||
oia = session.GetOIA();
|
||||
conn = session.GetConnection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFacadeInstantiationAndNamespaceIntegrity() {
|
||||
assertNotNull(session, "ECLSession should be created");
|
||||
assertNotNull(ps, "ECLPS facade should be non-null");
|
||||
assertNotNull(oia, "ECLOIA facade should be non-null");
|
||||
assertNotNull(conn, "ECLConnection facade should be non-null");
|
||||
assertNotNull(session.GetXfer(), "ECLXfer facade should be non-null");
|
||||
assertNotNull(session.GetScreenReco(), "ECLScreenReco facade should be non-null");
|
||||
|
||||
// Constants checking
|
||||
assertEquals(1, ECLConstants.PLANE_TEXT);
|
||||
assertEquals(2, ECLConstants.PLANE_COLOR);
|
||||
assertEquals(4, ECLConstants.PLANE_EXTENDED);
|
||||
assertEquals(1, ECLConstants.SEARCH_FORWARD);
|
||||
assertEquals(2, ECLConstants.SEARCH_BACKWARD);
|
||||
|
||||
// Connection metadata
|
||||
assertEquals("mainframe.test.local", conn.GetHost());
|
||||
assertEquals(23, conn.GetPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1BasedCoordinateConversion() throws ECLErr {
|
||||
assertEquals(24, ps.GetRows());
|
||||
assertEquals(80, ps.GetCols());
|
||||
assertEquals(1920, ps.GetSize());
|
||||
|
||||
// 1-based pos to row/col
|
||||
// pos 1 -> row 1, col 1
|
||||
assertEquals(1, ps.ConvertPosToRow(1));
|
||||
assertEquals(1, ps.ConvertPosToCol(1));
|
||||
assertEquals(1, ps.ConvertRowColToPos(1, 1));
|
||||
|
||||
// pos 80 -> row 1, col 80
|
||||
assertEquals(1, ps.ConvertPosToRow(80));
|
||||
assertEquals(80, ps.ConvertPosToCol(80));
|
||||
assertEquals(80, ps.ConvertRowColToPos(1, 80));
|
||||
|
||||
// pos 81 -> row 2, col 1
|
||||
assertEquals(2, ps.ConvertPosToRow(81));
|
||||
assertEquals(1, ps.ConvertPosToCol(81));
|
||||
assertEquals(81, ps.ConvertRowColToPos(2, 1));
|
||||
|
||||
// pos 1920 -> row 24, col 80
|
||||
assertEquals(24, ps.ConvertPosToRow(1920));
|
||||
assertEquals(80, ps.ConvertPosToCol(1920));
|
||||
assertEquals(1920, ps.ConvertRowColToPos(24, 80));
|
||||
|
||||
// ConvertPosToRowCol int[] array
|
||||
int[] rowCol = new int[2];
|
||||
ps.ConvertPosToRowCol(81, rowCol);
|
||||
assertEquals(2, rowCol[0]);
|
||||
assertEquals(1, rowCol[1]);
|
||||
|
||||
// Out of bounds pos checks
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertPosToRow(0));
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertPosToRow(1921));
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertRowColToPos(0, 1));
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertRowColToPos(25, 1));
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertRowColToPos(1, 0));
|
||||
assertThrows(ECLErr.class, () -> ps.ConvertRowColToPos(1, 81));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCursorPositioning1Based() throws ECLErr {
|
||||
// Initial cursor at pos 1 (row 1, col 1)
|
||||
ps.SetCursorPos(1);
|
||||
assertEquals(1, ps.GetCursorPos());
|
||||
assertEquals(1, ps.GetCursorRow());
|
||||
assertEquals(1, ps.GetCursorCol());
|
||||
|
||||
// Set by pos
|
||||
ps.SetCursorPos(85);
|
||||
assertEquals(85, ps.GetCursorPos());
|
||||
assertEquals(2, ps.GetCursorRow());
|
||||
assertEquals(5, ps.GetCursorCol());
|
||||
|
||||
// Set by row and col
|
||||
ps.SetCursorPos(5, 10);
|
||||
assertEquals(330, ps.GetCursorPos()); // (5-1)*80 + 10 = 330
|
||||
assertEquals(5, ps.GetCursorRow());
|
||||
assertEquals(10, ps.GetCursorCol());
|
||||
|
||||
// Invalid cursor pos throws ECLErr
|
||||
assertThrows(ECLErr.class, () -> ps.SetCursorPos(0));
|
||||
assertThrows(ECLErr.class, () -> ps.SetCursorPos(1921));
|
||||
assertThrows(ECLErr.class, () -> ps.SetCursorPos(0, 10));
|
||||
assertThrows(ECLErr.class, () -> ps.SetCursorPos(25, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndSetString1Based() throws ECLErr {
|
||||
// Write text at 1-based pos 1
|
||||
ps.SetString("LOGON APPLID(CICS01)", 1);
|
||||
assertEquals("LOGON APPLID(CICS01)", ps.GetString(1, 20));
|
||||
|
||||
// GetString with char buffer
|
||||
char[] buf = new char[5];
|
||||
int read = ps.GetString(buf, 5, 1);
|
||||
assertEquals(5, read);
|
||||
assertEquals("LOGON", new String(buf));
|
||||
|
||||
// Write text by (row, col)
|
||||
ps.SetString("PASSWORD", 2, 1);
|
||||
assertEquals("PASSWORD", ps.GetString(81, 8));
|
||||
assertEquals("PASSWORD", ps.GetString(2, 1, 8));
|
||||
|
||||
// Bounds checks
|
||||
assertThrows(ECLErr.class, () -> ps.GetString(0, 5));
|
||||
assertThrows(ECLErr.class, () -> ps.GetString(1921, 5));
|
||||
assertThrows(ECLErr.class, () -> ps.SetString("TEST", 0));
|
||||
assertThrows(ECLErr.class, () -> ps.SetString("TEST", 1921));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchText1Based() throws ECLErr {
|
||||
ps.SetString("ENTER USERID:", 1, 1);
|
||||
ps.SetString("ENTER PASSWORD:", 2, 1);
|
||||
|
||||
// Forward search from pos 1
|
||||
int found1 = ps.SearchText("USERID", ECLConstants.SEARCH_FORWARD, 1);
|
||||
assertEquals(7, found1); // "ENTER " is 6 chars, USERID starts at 7
|
||||
|
||||
// Forward search for PASSWORD
|
||||
int found2 = ps.SearchText("PASSWORD", ECLConstants.SEARCH_FORWARD, 1);
|
||||
assertEquals(87, found2); // 80 + 7 = 87
|
||||
|
||||
// Backward search
|
||||
int foundBack = ps.SearchText("USERID", ECLConstants.SEARCH_BACKWARD, 100);
|
||||
assertEquals(7, foundBack);
|
||||
|
||||
// Not found returns 0
|
||||
assertEquals(0, ps.SearchText("NOTPRESENT", ECLConstants.SEARCH_FORWARD, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendKeysAndMnemonics() throws ECLErr {
|
||||
// Verify send keys mnemonics array
|
||||
String[] mnemonics = ps.GetSendKeyMnemonics();
|
||||
assertNotNull(mnemonics);
|
||||
assertTrue(mnemonics.length > 20);
|
||||
|
||||
boolean hasEnter = false;
|
||||
boolean hasClear = false;
|
||||
boolean hasTab = false;
|
||||
for (String m : mnemonics) {
|
||||
if ("[enter]".equals(m)) hasEnter = true;
|
||||
if ("[clear]".equals(m)) hasClear = true;
|
||||
if ("[tab]".equals(m)) hasTab = true;
|
||||
}
|
||||
assertTrue(hasEnter, "Must have [enter] mnemonic");
|
||||
assertTrue(hasClear, "Must have [clear] mnemonic");
|
||||
assertTrue(hasTab, "Must have [tab] mnemonic");
|
||||
|
||||
// SendKeys to presentation space
|
||||
ps.SendKeys("ABC[enter]");
|
||||
// Verify buffer got ABC
|
||||
assertEquals("ABC", ps.GetString(1, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPSEventListenerDispatching() throws Exception {
|
||||
AtomicBoolean updatedCalled = new AtomicBoolean(false);
|
||||
AtomicInteger eventTypeSeen = new AtomicInteger(-1);
|
||||
|
||||
ECLPSListener listener = new ECLPSListener() {
|
||||
@Override
|
||||
public void PSNotifyEvent(ECLPSEvent event) {
|
||||
updatedCalled.set(true);
|
||||
eventTypeSeen.set(event.GetType());
|
||||
}
|
||||
};
|
||||
|
||||
ps.RegisterPSEvent(listener);
|
||||
|
||||
// Update presentation space
|
||||
ps.SetString("TESTING NOTIFY", 1);
|
||||
// Dispatch notifications through delegate
|
||||
ps.getDelegate().notifyPSUpdate(1, 1, 1, 14, false);
|
||||
|
||||
assertTrue(updatedCalled.get(), "PSNotifyEvent should be invoked");
|
||||
|
||||
// Unregister
|
||||
ps.UnregisterPSEvent(listener);
|
||||
updatedCalled.set(false);
|
||||
ps.getDelegate().notifyPSUpdate(1, 1, 1, 14, false);
|
||||
assertFalse(updatedCalled.get(), "PSNotifyEvent should not be called after Unregister");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOIANotificationsAndStatus() throws Exception {
|
||||
assertNotNull(oia);
|
||||
assertTrue(oia.IsCommError(), "IsCommError should be true when not connected");
|
||||
assertFalse(oia.IsInsertMode());
|
||||
assertTrue(oia.IsKeyLocked(), "IsKeyLocked should be true when disconnected");
|
||||
|
||||
// Clear inhibit override
|
||||
oia.getDelegate().setInputInhibited(ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
assertFalse(oia.IsKeyLocked(), "IsKeyLocked should be false when not inhibited");
|
||||
|
||||
AtomicBoolean oiaNotified = new AtomicBoolean(false);
|
||||
ECLOIANotify notify = (o, reason, state, text) -> {
|
||||
oiaNotified.set(true);
|
||||
};
|
||||
|
||||
oia.RegisterOIAEvent(notify);
|
||||
oia.getDelegate().notifyOIAChanged();
|
||||
|
||||
assertTrue(oiaNotified.get(), "OIA notification callback should be invoked");
|
||||
|
||||
oia.UnregisterOIAEvent(notify);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLFieldAndFieldList1Based() throws ECLErr {
|
||||
// Unformatted screen field list
|
||||
ECLFieldList fl = ps.GetFieldList();
|
||||
assertNotNull(fl);
|
||||
|
||||
// Screen Desc & Screen Reco
|
||||
ECLScreenDesc desc = new ECLScreenDesc();
|
||||
desc.AddString("ENTER USERID:", 1, 1);
|
||||
ps.SetString("ENTER USERID:", 1, 1);
|
||||
assertTrue(desc.Matches(ps.getDelegate(), oia.getDelegate()), "Matches ScreenDesc");
|
||||
|
||||
ECLScreenReco reco = session.GetScreenReco();
|
||||
assertNotNull(reco);
|
||||
assertTrue(reco.IsMatch(desc), "ScreenReco matches descriptor");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostPrintSessionInstantiation() throws ECLErr {
|
||||
Properties printProps = new Properties();
|
||||
printProps.setProperty(ECLSession.SESSION_HOST, "printhost.test.local");
|
||||
printProps.setProperty(ECLSession.SESSION_PORT, "515");
|
||||
ECLHostPrintSession printSession = new ECLHostPrintSession(printProps);
|
||||
|
||||
assertNotNull(printSession);
|
||||
assertEquals("printhost.test.local", printSession.GetHost());
|
||||
assertEquals(515, printSession.GetPort());
|
||||
}
|
||||
}
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.ecl.screenreco.*;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
|
||||
/**
|
||||
* Phase 23 test suite: Advanced Screen Recognition (ECLScreenDesc) Composite Engine & XML Schema.
|
||||
*/
|
||||
public class Phase23ScreenRecoCompositeTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private InputProcessor inputProcessor;
|
||||
private EbcdicTranslator translator;
|
||||
private ECLPS ps;
|
||||
private ECLOIA oia;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator("037");
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
inputProcessor = new InputProcessor(screen, translator, null);
|
||||
ps = new ECLPS(screen, inputProcessor, translator);
|
||||
oia = new ECLOIA(screen, inputProcessor, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDString absolute, rect, case-sense, wrap, and invert matching")
|
||||
public void testECLSDStringMatching() {
|
||||
ps.SetText("TSO/E LOGON", 1, 35);
|
||||
ps.SetText("ENTER USERID", 5, 10);
|
||||
|
||||
// 1. Absolute position matching at row 1, col 35
|
||||
ECLSDString sd1 = new ECLSDString("TSO/E LOGON", 1, 35, true);
|
||||
assertTrue(sd1.matches(ps, oia));
|
||||
|
||||
// Case-insensitive match
|
||||
ECLSDString sdCaseInsensitive = new ECLSDString("tso/e logon", 1, 35, false);
|
||||
assertTrue(sdCaseInsensitive.matches(ps, oia));
|
||||
|
||||
// Case-sensitive mismatch
|
||||
ECLSDString sdCaseSensitiveMismatch = new ECLSDString("tso/e logon", 1, 35, true);
|
||||
assertFalse(sdCaseSensitiveMismatch.matches(ps, oia));
|
||||
|
||||
// Invert match on mismatch -> true
|
||||
sdCaseSensitiveMismatch.setInvertMatch(true);
|
||||
assertTrue(sdCaseSensitiveMismatch.matches(ps, oia));
|
||||
|
||||
// 2. Rectangular region matching
|
||||
ECLSDString sdRect = new ECLSDString("USERID", 4, 1, 6, 80, false, false);
|
||||
assertTrue(sdRect.matches(ps, oia));
|
||||
|
||||
ECLSDString sdRectMiss = new ECLSDString("UNKNOWN", 4, 1, 6, 80, false, false);
|
||||
assertFalse(sdRectMiss.matches(ps, oia));
|
||||
|
||||
// 3. Search whole screen (row=0, col=0)
|
||||
ECLSDString sdWholeScreen = new ECLSDString("LOGON");
|
||||
assertTrue(sdWholeScreen.matches(ps, oia));
|
||||
|
||||
// 4. Linear buffer pos
|
||||
int pos = screen.rowColToAddress(0, 34) + 1; // 1-based pos
|
||||
ECLSDString sdPos = new ECLSDString("TSO/E", pos, true);
|
||||
assertTrue(sdPos.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDCursor location match (row, col) and pos")
|
||||
public void testECLSDCursorMatching() {
|
||||
ps.setCursorPos(7, 18); // 0-based row 7, col 18 = 1-based row 8, col 19
|
||||
|
||||
ECLSDCursor curMatch = new ECLSDCursor(8, 19);
|
||||
assertTrue(curMatch.matches(ps, oia));
|
||||
|
||||
ECLSDCursor curMismatch = new ECLSDCursor(1, 1);
|
||||
assertFalse(curMismatch.matches(ps, oia));
|
||||
|
||||
curMismatch.setInvertMatch(true);
|
||||
assertTrue(curMismatch.matches(ps, oia));
|
||||
|
||||
// By linear position
|
||||
int pos = screen.rowColToAddress(7, 18) + 1;
|
||||
ECLSDCursor curPos = new ECLSDCursor(pos);
|
||||
assertTrue(curPos.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDFields and ECLSDInputFields field count matching")
|
||||
public void testECLSDFieldsAndInputFields() {
|
||||
// Create formatted fields on screen
|
||||
screen.setFieldAttribute(0, (byte) 0x20); // protected
|
||||
screen.setFieldAttribute(80, (byte) 0x00); // unprotected input
|
||||
screen.setFieldAttribute(160, (byte) 0x00); // unprotected input
|
||||
ps.getFieldList().refresh();
|
||||
|
||||
assertEquals(3, ps.getFieldList().getFieldCount());
|
||||
|
||||
// Total field count
|
||||
ECLSDFields fields3 = new ECLSDFields(3);
|
||||
assertTrue(fields3.matches(ps, oia));
|
||||
|
||||
ECLSDFields fieldsWrong = new ECLSDFields(5);
|
||||
assertFalse(fieldsWrong.matches(ps, oia));
|
||||
fieldsWrong.setInvertMatch(true);
|
||||
assertTrue(fieldsWrong.matches(ps, oia));
|
||||
|
||||
// Unprotected input fields count = 2
|
||||
ECLSDInputFields inFields2 = new ECLSDInputFields(2);
|
||||
assertTrue(inFields2.matches(ps, oia));
|
||||
|
||||
ECLSDInputFields inFieldsWrong = new ECLSDInputFields(3);
|
||||
assertFalse(inFieldsWrong.matches(ps, oia));
|
||||
inFieldsWrong.setInvertMatch(true);
|
||||
assertTrue(inFieldsWrong.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDOIA OIA status matching (NOTINHIBITED, DONTCARE, input=true/false)")
|
||||
public void testECLSDOIAMatching() {
|
||||
oia.setInputInhibited(ECLOIA.INHIBIT_NOTINHIBITED);
|
||||
|
||||
ECLSDOIA oiaNot = new ECLSDOIA(ECLSDOIA.OIA_NOTINHIBITED);
|
||||
assertTrue(oiaNot.matches(ps, oia));
|
||||
|
||||
ECLSDOIA oiaDontCare = new ECLSDOIA(ECLSDOIA.OIA_DONTCARE);
|
||||
assertTrue(oiaDontCare.matches(ps, oia));
|
||||
|
||||
ECLSDOIA oiaInputTrue = new ECLSDOIA("true", false);
|
||||
assertTrue(oiaInputTrue.matches(ps, oia));
|
||||
|
||||
// Lock keyboard (system lock)
|
||||
oia.setInputInhibited(ECLOIA.INHIBIT_SYSTEM_LOCK);
|
||||
assertFalse(oiaNot.matches(ps, oia));
|
||||
assertFalse(oiaInputTrue.matches(ps, oia));
|
||||
|
||||
// Invert match should now match because input is inhibited
|
||||
oiaNot.setInvertMatch(true);
|
||||
assertTrue(oiaNot.matches(ps, oia));
|
||||
|
||||
// Explicit inhibit code check
|
||||
ECLSDOIA oiaSysLock = new ECLSDOIA();
|
||||
oiaSysLock.setExplicitInhibitStatus(ECLOIA.INHIBIT_SYSTEM_LOCK);
|
||||
assertTrue(oiaSysLock.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDChecksum screen buffer hash matching")
|
||||
public void testECLSDChecksumMatching() {
|
||||
screen.setFieldAttribute(0, (byte) 0x20);
|
||||
screen.setFieldAttribute(100, (byte) 0x00);
|
||||
screen.setFieldAttribute(300, (byte) 0x20);
|
||||
ps.getFieldList().refresh();
|
||||
|
||||
int checksum = ECLSDChecksum.CalculateChecksum(ps.getFieldList(), ps.getSize());
|
||||
assertTrue(checksum != 0);
|
||||
|
||||
ECLSDChecksum sdChecksum = new ECLSDChecksum(checksum);
|
||||
assertTrue(sdChecksum.matches(ps, oia));
|
||||
|
||||
ECLSDChecksum sdWrong = new ECLSDChecksum(checksum + 1);
|
||||
assertFalse(sdWrong.matches(ps, oia));
|
||||
sdWrong.setInvertMatch(true);
|
||||
assertTrue(sdWrong.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDBlock multi-line block text matching")
|
||||
public void testECLSDBlockMatching() {
|
||||
ps.SetText("IBM MAINFRAME", 10, 5);
|
||||
ps.SetText("SYSTEM/390", 11, 5);
|
||||
|
||||
String[] blockLines = new String[] { "IBM MAINFRAME", "SYSTEM/390" };
|
||||
|
||||
// Absolute block match
|
||||
ECLSDBlock blkAbs = new ECLSDBlock(blockLines, 10, 5, true);
|
||||
assertTrue(blkAbs.matches(ps, oia));
|
||||
|
||||
// Absolute block mismatch
|
||||
ECLSDBlock blkAbsMiss = new ECLSDBlock(blockLines, 10, 6, true);
|
||||
assertFalse(blkAbsMiss.matches(ps, oia));
|
||||
|
||||
// In-rect block match
|
||||
ECLSDBlock blkRect = new ECLSDBlock(1, 1, 24, 80, true, false);
|
||||
blkRect.setStrings(blockLines);
|
||||
assertTrue(blkRect.matches(ps, oia));
|
||||
|
||||
blkRect.setInvertMatch(true);
|
||||
assertFalse(blkRect.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: ECLSDAttrib plane and attribute matching")
|
||||
public void testECLSDAttribMatching() {
|
||||
screen.setFieldAttribute(0, (byte) 0x20); // row 1, col 1 = protected (0x20)
|
||||
ps.getFieldList().refresh();
|
||||
|
||||
ECLSDAttrib sdAttrib = new ECLSDAttrib((char) 0x20, 1, 1, ECLSDAttrib.FIELD_PLANE);
|
||||
assertTrue(sdAttrib.matches(ps, oia));
|
||||
|
||||
ECLSDAttrib sdAttribWrong = new ECLSDAttrib((char) 0x00, 1, 1, ECLSDAttrib.FIELD_PLANE);
|
||||
assertFalse(sdAttribWrong.matches(ps, oia));
|
||||
sdAttribWrong.setInvertMatch(true);
|
||||
assertTrue(sdAttribWrong.matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.2: Composite ECLScreenDesc matching and XML round-trip (<description> schema)")
|
||||
public void testCompositeAndXmlRoundtrip() {
|
||||
// Set up host screen
|
||||
ps.SetText("TSO/E LOGON", 1, 35);
|
||||
ps.setCursorPos(7, 18); // row 8, col 19
|
||||
oia.setInputInhibited(ECLOIA.INHIBIT_NOTINHIBITED);
|
||||
screen.setFieldAttribute(0, (byte) 0x20); // 1 field
|
||||
ps.getFieldList().refresh();
|
||||
|
||||
// 1. Programmatic Composite ECLScreenDesc
|
||||
ECLScreenDesc desc = new ECLScreenDesc();
|
||||
desc.AddString("TSO/E LOGON", 1, 35, false);
|
||||
desc.AddCursorPos(8, 19);
|
||||
desc.AddOIAStatus(ECLOIA.INHIBIT_NOTINHIBITED);
|
||||
desc.AddNumFields(1);
|
||||
|
||||
assertEquals(4, desc.Size());
|
||||
assertTrue(desc.Matches(ps, oia));
|
||||
|
||||
// 2. Check XML serialization
|
||||
String xml = desc.getXML();
|
||||
assertNotNull(xml);
|
||||
assertTrue(xml.contains("<description>"));
|
||||
assertTrue(xml.contains("<string"));
|
||||
assertTrue(xml.contains("value=\"TSO/E LOGON\""));
|
||||
assertTrue(xml.contains("<cursor"));
|
||||
assertTrue(xml.contains("<oia"));
|
||||
assertTrue(xml.contains("<numfields"));
|
||||
assertTrue(xml.contains("</description>"));
|
||||
|
||||
// 3. Test XML parsing matching exact HoD schema from Phase 23 prompt
|
||||
String sampleXml =
|
||||
"<description>\n" +
|
||||
" <string value=\"TSO/E LOGON\" row=\"1\" col=\"35\" casesense=\"false\"/>\n" +
|
||||
" <cursor row=\"8\" col=\"19\"/>\n" +
|
||||
" <oia input=\"true\"/>\n" +
|
||||
" <numfields value=\"1\"/>\n" +
|
||||
"</description>";
|
||||
|
||||
ECLScreenDesc parsedDesc = new ECLScreenDesc(sampleXml);
|
||||
assertEquals(4, parsedDesc.Size());
|
||||
assertTrue(parsedDesc.Matches(ps, oia));
|
||||
|
||||
// If screen text changes, match fails
|
||||
ps.SetText("DIFFERENT SCR", 1, 35);
|
||||
assertFalse(parsedDesc.Matches(ps, oia));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1 & 23.2: ECLScreenReco dynamic registration triggers on PS and OIA updates")
|
||||
public void testScreenRecoDynamicCallbacks() {
|
||||
ECLScreenDesc desc = new ECLScreenDesc();
|
||||
desc.AddString("READY", 1, 1);
|
||||
desc.AddOIAStatus(ECLOIA.INHIBIT_NOTINHIBITED);
|
||||
|
||||
ECLScreenReco reco = new ECLScreenReco(ps, oia);
|
||||
AtomicBoolean matched = new AtomicBoolean(false);
|
||||
|
||||
reco.RegisterScreen(desc, event -> matched.set(true));
|
||||
|
||||
// Screen is not matching yet
|
||||
assertFalse(matched.get());
|
||||
|
||||
// Screen text updated but OIA locked
|
||||
ps.SetText("READY", 1, 1);
|
||||
oia.setInputInhibited(ECLOIA.INHIBIT_SYSTEM_LOCK);
|
||||
ps.notifyKeyUnlocked();
|
||||
assertFalse(matched.get());
|
||||
|
||||
// Unlock OIA -> triggers reco callback
|
||||
oia.setInputInhibited(ECLOIA.INHIBIT_NOTINHIBITED);
|
||||
assertTrue(matched.get());
|
||||
|
||||
reco.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("23.1: Drop-in compatibility with com.ibm.eNetwork.ECL.screenreco facade classes")
|
||||
public void testHaclFacadesCompatibility() {
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDString s = new com.ibm.eNetwork.ECL.screenreco.ECLSDString("HELLO", 1, 1);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDCursor c = new com.ibm.eNetwork.ECL.screenreco.ECLSDCursor(1, 1);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDFields f = new com.ibm.eNetwork.ECL.screenreco.ECLSDFields(5);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDInputFields inf = new com.ibm.eNetwork.ECL.screenreco.ECLSDInputFields(2);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDOIA o = new com.ibm.eNetwork.ECL.screenreco.ECLSDOIA("NOTINHIBITED", false);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDChecksum cs = new com.ibm.eNetwork.ECL.screenreco.ECLSDChecksum(100);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDBlock blk = new com.ibm.eNetwork.ECL.screenreco.ECLSDBlock(1, 1, false, false);
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDBlockString bs = new com.ibm.eNetwork.ECL.screenreco.ECLSDBlockString("LINE");
|
||||
com.ibm.eNetwork.ECL.screenreco.ECLSDAttrib attr = new com.ibm.eNetwork.ECL.screenreco.ECLSDAttrib((char) 0x20, 1, 1, 16);
|
||||
|
||||
assertTrue(s instanceof ECLScreenDescriptor);
|
||||
assertTrue(c instanceof ECLScreenDescriptor);
|
||||
assertTrue(f instanceof ECLScreenDescriptor);
|
||||
assertTrue(inf instanceof ECLScreenDescriptor);
|
||||
assertTrue(o instanceof ECLScreenDescriptor);
|
||||
assertTrue(cs instanceof ECLScreenDescriptor);
|
||||
assertTrue(blk instanceof ECLScreenDescriptor);
|
||||
assertTrue(attr instanceof ECLScreenDescriptor);
|
||||
|
||||
assertEquals("HELLO", s.GetString());
|
||||
assertEquals(1, c.GetRow());
|
||||
assertEquals(5, f.GetNum());
|
||||
assertEquals(2, inf.GetNum());
|
||||
assertEquals(ECLSDOIA.OIA_NOTINHIBITED, o.GetOIAType());
|
||||
assertEquals(100, cs.GetValue());
|
||||
assertEquals(1, blk.getSRow());
|
||||
assertEquals("LINE", bs.GetString());
|
||||
assertEquals(0x20, attr.getAttrib());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.ecl.util.SendKeyTokenizer;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class SendKeyTokenizerPhase21Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor inputProcessor;
|
||||
private ECLSession session;
|
||||
private ECLPS ps;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ConnectionConfig config = new ConnectionConfig("mainframe.example.com", 23, TerminalModel.IBM_3279_4);
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
session = new ECLSession(client);
|
||||
ps = session.GetPS();
|
||||
screen = client.getScreenBuffer();
|
||||
translator = client.getTranslator();
|
||||
inputProcessor = client.getInputProcessor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenizerBasicPlainAndBracketed() throws ECLErr {
|
||||
SendKeyTokenizer tok = new SendKeyTokenizer("LOGON APPLID[enter]");
|
||||
assertTrue(tok.hasMoreTokens());
|
||||
assertEquals("LOGON APPLID", tok.nextToken());
|
||||
assertTrue(tok.hasMoreTokens());
|
||||
assertEquals("[enter]", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
assertNull(tok.nextToken());
|
||||
assertEquals("", tok.remainder());
|
||||
|
||||
tok.setSendKeyString("USER[tab]PASS[enter]");
|
||||
assertEquals("USER", tok.nextToken());
|
||||
assertEquals("[tab]", tok.nextToken());
|
||||
assertEquals("PASS", tok.nextToken());
|
||||
assertEquals("[enter]", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenizerEscapedBrackets() throws ECLErr {
|
||||
SendKeyTokenizer tok = new SendKeyTokenizer("[[");
|
||||
assertEquals("[", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
|
||||
tok.setSendKeyString("]]");
|
||||
assertEquals("]", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
|
||||
tok.setSendKeyString("A[[B]]C");
|
||||
assertEquals("A", tok.nextToken());
|
||||
assertEquals("[", tok.nextToken());
|
||||
assertEquals("B", tok.nextToken());
|
||||
assertEquals("]", tok.nextToken());
|
||||
assertEquals("C", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
|
||||
// Escaped [[enter]] should yield "[", "enter", "]" rather than the AID key [enter]
|
||||
tok.setSendKeyString("[[enter]]");
|
||||
assertEquals("[", tok.nextToken());
|
||||
assertEquals("enter", tok.nextToken());
|
||||
assertEquals("]", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenizerExceptions() {
|
||||
// Unterminated bracket
|
||||
SendKeyTokenizer tok1 = new SendKeyTokenizer("[enter");
|
||||
ECLErr err1 = assertThrows(ECLErr.class, tok1::nextToken);
|
||||
assertEquals("ECL0012", err1.getID());
|
||||
assertEquals(1, err1.getReasonCode());
|
||||
assertEquals(12, err1.getMsgNumber());
|
||||
|
||||
// Single opening bracket at EOF
|
||||
SendKeyTokenizer tok2 = new SendKeyTokenizer("[");
|
||||
ECLErr err2 = assertThrows(ECLErr.class, tok2::nextToken);
|
||||
assertEquals("ECL0012", err2.getID());
|
||||
assertEquals(1, err2.getReasonCode());
|
||||
|
||||
// Single closing bracket at EOF
|
||||
SendKeyTokenizer tok3 = new SendKeyTokenizer("]");
|
||||
ECLErr err3 = assertThrows(ECLErr.class, tok3::nextToken);
|
||||
assertEquals("ECL0012", err3.getID());
|
||||
assertEquals(1, err3.getReasonCode());
|
||||
|
||||
// Unescaped closing bracket followed by characters
|
||||
SendKeyTokenizer tok4 = new SendKeyTokenizer("]invalid");
|
||||
ECLErr err4 = assertThrows(ECLErr.class, tok4::nextToken);
|
||||
assertEquals("ECL0012", err4.getID());
|
||||
assertEquals(1, err4.getReasonCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenizerNoAidAndCharMode() throws ECLErr {
|
||||
SendKeyTokenizer tok = new SendKeyTokenizer("A[enter]B");
|
||||
assertEquals("A", tok.nextTokenNoAid());
|
||||
assertEquals("[", tok.nextTokenNoAid());
|
||||
assertEquals("e", tok.nextTokenNoAid());
|
||||
|
||||
// Character mode (chunkPlainText = false) matching HoD 1:1 internal loop
|
||||
tok.setSendKeyString("ABC[tab]");
|
||||
tok.setChunkPlainText(false);
|
||||
assertEquals("A", tok.nextToken());
|
||||
assertEquals("B", tok.nextToken());
|
||||
assertEquals("C", tok.nextToken());
|
||||
assertEquals("[tab]", tok.nextToken());
|
||||
assertFalse(tok.hasMoreTokens());
|
||||
|
||||
// Single digit keypad shortcut [0]..[9] -> "0".."9"
|
||||
tok.setSendKeyString("[5]");
|
||||
tok.setChunkPlainText(true);
|
||||
assertEquals("5", tok.nextToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLErrConstructorsAndParity() {
|
||||
ECLErr err = new ECLErr("SendKeyTokenizer:1", "ECL0012", 1);
|
||||
assertEquals("SendKeyTokenizer:1", err.getLocation());
|
||||
assertEquals("SendKeyTokenizer:1", err.GetLocation());
|
||||
assertEquals("ECL0012", err.getID());
|
||||
assertEquals("ECL0012", err.GetID());
|
||||
assertEquals(1, err.getReasonCode());
|
||||
assertEquals(1, err.GetReasonCode());
|
||||
assertEquals(12, err.getMsgNumber());
|
||||
assertEquals(12, err.GetMsgNumber());
|
||||
assertNotNull(err.toString());
|
||||
|
||||
ECLErr err2 = new ECLErr("ECLPS", "ECL0010");
|
||||
assertEquals("ECLPS", err2.getLocation());
|
||||
assertEquals("ECL0010", err2.getID());
|
||||
|
||||
ECLErr err3 = new ECLErr("ECLPS", "ECL0010", "param", 2);
|
||||
assertEquals(2, err3.getReasonCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendKeysEscapingInScreen() {
|
||||
// Setup unprotected field at 0
|
||||
screen.setCellFA(0, (byte) 0x40);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
// Send keys with escaped brackets
|
||||
inputProcessor.sendKeys("ABC[[XYZ]]123");
|
||||
|
||||
assertEquals("ABC[XYZ]123", ps.getString(1, 11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test120MnemonicTokenTableInInputProcessor() {
|
||||
screen.clear();
|
||||
// Setup two fields: field 1 at 0 (len 10), field 2 at 11 (len 10)
|
||||
screen.setCellFA(0, (byte) 0x40);
|
||||
screen.setCellFA(11, (byte) 0x40);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
// Test typing and tab
|
||||
inputProcessor.sendKeys("HELLO[tab]WORLD");
|
||||
assertEquals("HELLO", ps.getString(1, 5));
|
||||
assertEquals("WORLD", ps.getString(12, 5));
|
||||
|
||||
// Test backtab: first backtab moves to beginning of current field (12)
|
||||
inputProcessor.sendKeys("[backtab]");
|
||||
assertEquals(12, screen.getCursorAddress());
|
||||
|
||||
// Second backtab moves to beginning of previous field (1)
|
||||
inputProcessor.sendKeys("[backtab]");
|
||||
assertEquals(1, screen.getCursorAddress());
|
||||
|
||||
// Test bof and eof
|
||||
inputProcessor.sendKeys("[eof]");
|
||||
assertEquals(6, screen.getCursorAddress()); // end of "HELLO"
|
||||
|
||||
inputProcessor.sendKeys("[bof]");
|
||||
assertEquals(1, screen.getCursorAddress()); // start of field
|
||||
|
||||
// Test erasefld
|
||||
inputProcessor.sendKeys("[erasefld]");
|
||||
assertEquals(1, screen.getCursorAddress());
|
||||
assertEquals(" ", ps.getString(1, 5));
|
||||
|
||||
// Test dup and fieldmark (dup enters DUP at 1 and tabs to next field at 12)
|
||||
inputProcessor.sendKeys("[dup][fieldmark]");
|
||||
assertEquals(0x1C, screen.getCell(1).ec & 0xFF);
|
||||
assertEquals(0x1E, screen.getCell(12).ec & 0xFF);
|
||||
|
||||
// Test numeric keypad tokens
|
||||
screen.setCursorAddress(1);
|
||||
inputProcessor.sendKeys("[keypad1][keypad2][keypad.][keypad-]");
|
||||
assertEquals("12.-", ps.getString(1, 4));
|
||||
|
||||
// Test enterreset
|
||||
inputProcessor.setKeyboardLocked(true);
|
||||
inputProcessor.sendKeys("[enterreset]");
|
||||
assertFalse(inputProcessor.isKeyboardLocked());
|
||||
|
||||
// Test docmode, wordwrap, apl
|
||||
boolean docBefore = screen.isEntryAssistDOCmode();
|
||||
inputProcessor.sendKeys("[docmode]");
|
||||
assertEquals(!docBefore, screen.isEntryAssistDOCmode());
|
||||
|
||||
boolean wrapBefore = screen.isEntryAssistWordWrap();
|
||||
inputProcessor.sendKeys("[wordwrap]");
|
||||
assertEquals(!wrapBefore, screen.isEntryAssistWordWrap());
|
||||
|
||||
// Test dspsosi
|
||||
inputProcessor.sendKeys("[dspsosi]");
|
||||
|
||||
// Test altview
|
||||
inputProcessor.sendKeys("[altview]");
|
||||
|
||||
// Test cursor selection and light pen
|
||||
screen.setCellFA(30, (byte) (0x40 | DS3270Constants.FA_INT_NORM_SEL));
|
||||
screen.getCell(31).ec = (byte) 0x6F; // '?'
|
||||
screen.getCell(31).ucs4 = '?';
|
||||
screen.setCursorAddress(31);
|
||||
|
||||
inputProcessor.sendKeys("[selfld]");
|
||||
assertEquals('>', (char) screen.getCell(31).ucs4);
|
||||
assertTrue((screen.getCell(30).fa & DS3270Constants.FA_MODIFY) != 0);
|
||||
|
||||
inputProcessor.sendKeys("[deselfld]");
|
||||
assertEquals('?', (char) screen.getCell(31).ucs4);
|
||||
assertEquals(0, screen.getCell(30).fa & DS3270Constants.FA_MODIFY);
|
||||
|
||||
// Test safe handling of 30+ extended HoD tokens (bidi, print, gui, layers)
|
||||
inputProcessor.sendKeys("[screenrev][latinlayer][bidilayer][push][endpush][autopush][fieldshape][prtestpage][copy][paste]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendCharactersPacingAndPositioning() {
|
||||
screen.clear();
|
||||
screen.setCellFA(0, (byte) 0x40);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Paced typing with 10ms delay over 4 tokens
|
||||
ps.sendCharacters("TEST", 10);
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
assertEquals("TEST", ps.getString(1, 4));
|
||||
assertTrue(elapsed >= 25, "Elapsed time should reflect paced typing delay: " + elapsed + "ms");
|
||||
|
||||
// Test SendKeys / SendCharacters with 1-based pos
|
||||
screen.clear();
|
||||
screen.setCellFA(9, (byte) 0x40); // FA at pos 10, data starts at pos 11
|
||||
ps.SendKeys("HOD", 11);
|
||||
assertEquals("HOD", ps.getString(10, 3));
|
||||
|
||||
ps.SendCharacters("123", 11, 5);
|
||||
assertEquals("123", ps.getString(10, 3));
|
||||
|
||||
// Test SendKeysNoAid
|
||||
ps.SendKeysNoAid("XYZ", false);
|
||||
assertEquals("XYZ", ps.getString(13, 3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
package haus.nightmare.lib3270j.printer;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Targeted unit tests for Phase 20: IBM 3287/3286 Host Printing Protocol (SCS LU-1 & DSC/DSE LU-3).
|
||||
* Validates 1:1 parity with IBM Host On-Demand v14 com.ibm.eNetwork.ECL.tn3270p.*
|
||||
*/
|
||||
public class Phase20HostPrintingTest {
|
||||
|
||||
private PrinterConfig config;
|
||||
private PD3270 pd;
|
||||
private EbcdicTranslator translator;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new PrinterConfig("localhost", 23);
|
||||
pd = new PD3270(config);
|
||||
translator = new EbcdicTranslator("037");
|
||||
pd.openPrinter(null); // Capture in memory
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 20.1 SCS Control Orders Execution (LU-1)
|
||||
// ==========================================
|
||||
|
||||
@Test
|
||||
public void testSCSHorizontalFormatSHF() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// Standard SNA SHF (0x2B 0xC1): count=6, MPP=132, LM=10, RM=120, tabs at 20, 30
|
||||
byte[] shf = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SHF,
|
||||
0x06, // count
|
||||
(byte) 132, // MPP
|
||||
10, // Left Margin
|
||||
120, // Right Margin
|
||||
20, // Tab 1
|
||||
30 // Tab 2
|
||||
};
|
||||
|
||||
scs.processHostData(shf, 0, shf.length);
|
||||
assertEquals(132, scs.getLineLength());
|
||||
assertEquals(10, scs.getLeftMargin());
|
||||
assertEquals(120, scs.getRightMargin());
|
||||
assertEquals(10, scs.getCurrentColumn()); // Clamped to LM
|
||||
|
||||
// Test Horizontal Tab (HT 0x05)
|
||||
byte[] ht = new byte[]{ PrinterConstants.SCS_HT };
|
||||
scs.processHostData(ht, 0, ht.length);
|
||||
assertEquals(20, scs.getCurrentColumn());
|
||||
|
||||
scs.processHostData(ht, 0, ht.length);
|
||||
assertEquals(30, scs.getCurrentColumn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSVerticalFormatSVF() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// Standard SNA SVF (0x2B 0xC2): count=6, MPL=66, TM=3, BM=60, tabs at 15, 30
|
||||
byte[] svf = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SVF,
|
||||
0x06, // count
|
||||
66, // MPL
|
||||
3, // Top Margin
|
||||
60, // Bottom Margin
|
||||
15, // VTab 1
|
||||
30 // VTab 2
|
||||
};
|
||||
|
||||
scs.processHostData(svf, 0, svf.length);
|
||||
assertEquals(66, scs.getPageLength());
|
||||
assertEquals(3, scs.getTopMargin());
|
||||
assertEquals(60, scs.getBottomMargin());
|
||||
assertEquals(3, scs.getCurrentRow()); // Clamped to TM
|
||||
|
||||
// Test Vertical Tab (VT 0x0B)
|
||||
byte[] vt = new byte[]{ PrinterConstants.SCS_VT };
|
||||
scs.processHostData(vt, 0, vt.length);
|
||||
assertEquals(15, scs.getCurrentRow());
|
||||
|
||||
scs.processHostData(vt, 0, vt.length);
|
||||
assertEquals(30, scs.getCurrentRow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSLineDensitySLD() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// SLD (0x2B 0xC6): 12 points = 6 LPI
|
||||
byte[] sld6 = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SLD,
|
||||
0x02, // count
|
||||
12 // points (12 = 6 LPI)
|
||||
};
|
||||
scs.processHostData(sld6, 0, sld6.length);
|
||||
assertEquals(6, scs.getLinesPerInch());
|
||||
assertEquals(6, pd.getLPI());
|
||||
|
||||
// SLD (0x2B 0xC6): 9 points = 8 LPI
|
||||
byte[] sld8 = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SLD,
|
||||
0x02, // count
|
||||
9 // points (9 = 8 LPI)
|
||||
};
|
||||
scs.processHostData(sld8, 0, sld8.length);
|
||||
assertEquals(8, scs.getLinesPerInch());
|
||||
assertEquals(8, pd.getLPI());
|
||||
|
||||
// SLD with count=1 (defaults to 6 LPI)
|
||||
byte[] sldDefault = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SLD,
|
||||
0x01
|
||||
};
|
||||
scs.processHostData(sldDefault, 0, sldDefault.length);
|
||||
assertEquals(6, scs.getLinesPerInch());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSPrintDensitySPD() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// SPD (0x2B 0xD2): standard parameter format count=4, type=0x29, density=12 CPI
|
||||
byte[] spd12 = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SPD,
|
||||
0x04,
|
||||
PrinterConstants.SPD_TYPE_ID, // 0x29
|
||||
0x00,
|
||||
12
|
||||
};
|
||||
scs.processHostData(spd12, 0, spd12.length);
|
||||
assertEquals(12, scs.getCharsPerInch());
|
||||
assertEquals(12, pd.getCPI());
|
||||
assertFalse(scs.isDoubleWidth());
|
||||
|
||||
// SPD (0x2B 0xD2): density=15 CPI
|
||||
byte[] spd15 = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SPD,
|
||||
0x04,
|
||||
PrinterConstants.SPD_TYPE_ID,
|
||||
0x00,
|
||||
15
|
||||
};
|
||||
scs.processHostData(spd15, 0, spd15.length);
|
||||
assertEquals(15, scs.getCharsPerInch());
|
||||
assertFalse(scs.isDoubleWidth());
|
||||
|
||||
// SPD density=5 CPI (triggers double-width 10 CPI)
|
||||
byte[] spd5 = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_SPD,
|
||||
0x04,
|
||||
PrinterConstants.SPD_TYPE_ID,
|
||||
0x00,
|
||||
5
|
||||
};
|
||||
scs.processHostData(spd5, 0, spd5.length);
|
||||
assertEquals(10, scs.getCharsPerInch());
|
||||
assertTrue(scs.isDoubleWidth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSTransparentDataTRN() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// TRN (0x35): bypass translation and emit raw binary escape bytes
|
||||
byte[] rawPayload = new byte[]{ 0x1B, 0x5B, 0x32, 0x4A }; // ANSI Clear Screen ESC [ 2 J
|
||||
byte[] trn = new byte[]{
|
||||
PrinterConstants.SCS_TRN,
|
||||
(byte) rawPayload.length,
|
||||
rawPayload[0], rawPayload[1], rawPayload[2], rawPayload[3]
|
||||
};
|
||||
|
||||
scs.processHostData(trn, 0, trn.length);
|
||||
byte[] captured = pd.getCapturedBytes();
|
||||
assertNotNull(captured);
|
||||
assertTrue(captured.length >= 4);
|
||||
assertEquals(0x1B, captured[0]);
|
||||
assertEquals(0x5B, captured[1]);
|
||||
assertEquals(0x32, captured[2]);
|
||||
assertEquals(0x4A, captured[3]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSFormFeedAndPositions() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
scs.setHorizontalMargins(5, 80);
|
||||
scs.setVerticalMargins(2, 60);
|
||||
|
||||
// Print some characters
|
||||
byte[] text = translator.stringToEbcdic("LINE 1");
|
||||
scs.processHostData(text, 0, text.length);
|
||||
|
||||
// Form Feed
|
||||
scs.processHostData(new byte[]{ PrinterConstants.SCS_FF }, 0, 1);
|
||||
|
||||
assertEquals(1, pd.getPageCount());
|
||||
assertEquals(2, scs.getCurrentRow()); // Reset to TM
|
||||
assertEquals(5, scs.getCurrentColumn()); // Reset to LM
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSCSPresentationPositionPP() {
|
||||
PrintSCS3270 scs = new PrintSCS3270(config, pd, translator);
|
||||
|
||||
// PP (0x34): absolute horizontal column 35
|
||||
byte[] ppHoriz = new byte[]{
|
||||
PrinterConstants.SCS_PP,
|
||||
(byte) PrinterConstants.PP_HORIZ_ABS,
|
||||
35
|
||||
};
|
||||
scs.processHostData(ppHoriz, 0, ppHoriz.length);
|
||||
assertEquals(35, scs.getCurrentColumn());
|
||||
|
||||
// PP (0x34): relative horizontal +10
|
||||
byte[] ppHorizRel = new byte[]{
|
||||
PrinterConstants.SCS_PP,
|
||||
(byte) PrinterConstants.PP_HORIZ_REL,
|
||||
10
|
||||
};
|
||||
scs.processHostData(ppHorizRel, 0, ppHorizRel.length);
|
||||
assertEquals(45, scs.getCurrentColumn());
|
||||
|
||||
// PP (0x34): absolute vertical row 5
|
||||
byte[] ppVert = new byte[]{
|
||||
PrinterConstants.SCS_PP,
|
||||
(byte) PrinterConstants.PP_VERT_ABS,
|
||||
5
|
||||
};
|
||||
scs.processHostData(ppVert, 0, ppVert.length);
|
||||
assertEquals(5, scs.getCurrentRow());
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 20.2 DSC / DSE Buffer Processing (LU-3)
|
||||
// ==========================================
|
||||
|
||||
@Test
|
||||
public void testLU3WriteAndNonDisplayFieldMasking() throws Exception {
|
||||
PrintPS3270 ps = new PrintPS3270(config, pd, translator);
|
||||
|
||||
ByteArrayOutputStream ds = new ByteArrayOutputStream();
|
||||
ds.write(DS3270Constants.CMD_ERASE_WRITE);
|
||||
ds.write(PrinterConstants.WCC_START_PRINT_BIT); // WCC: Start print
|
||||
|
||||
// SBA to (0, 0)
|
||||
ds.write(PrinterConstants.ORDER_SBA);
|
||||
ds.write(0x40);
|
||||
ds.write(0x40);
|
||||
|
||||
// SF with visible attribute (0xC0: unprotected, display)
|
||||
ds.write(PrinterConstants.ORDER_SF);
|
||||
ds.write(0xC0);
|
||||
ds.write(translator.stringToEbcdic("PUBLIC"));
|
||||
|
||||
// SF with non-display attribute (0x4C: protected, non-display)
|
||||
ds.write(PrinterConstants.ORDER_SF);
|
||||
ds.write(0x4C);
|
||||
ds.write(translator.stringToEbcdic("SECRET"));
|
||||
|
||||
// SF with visible attribute again
|
||||
ds.write(PrinterConstants.ORDER_SF);
|
||||
ds.write(0xC0);
|
||||
ds.write(translator.stringToEbcdic("VISIBLE"));
|
||||
|
||||
byte[] record = ds.toByteArray();
|
||||
ps.process3270PrintDS(record, 0, record.length);
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertNotNull(captured);
|
||||
assertTrue(captured.contains("PUBLIC"));
|
||||
assertFalse(captured.contains("SECRET")); // Masked as blanks!
|
||||
assertTrue(captured.contains("VISIBLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLU3EraseWriteAndEORFlush() throws Exception {
|
||||
PrintPS3270 ps = new PrintPS3270(config, pd, translator);
|
||||
|
||||
// Write first record without Start Print bit
|
||||
ByteArrayOutputStream ds = new ByteArrayOutputStream();
|
||||
ds.write(DS3270Constants.CMD_WRITE);
|
||||
ds.write(0x00); // WCC without start print
|
||||
ds.write(PrinterConstants.ORDER_SBA);
|
||||
ds.write(0x40);
|
||||
ds.write(0x40);
|
||||
ds.write(translator.stringToEbcdic("RECORD 1 DATA"));
|
||||
|
||||
byte[] r1 = ds.toByteArray();
|
||||
ps.process3270PrintDS(r1, 0, r1.length);
|
||||
|
||||
// EOR should flush formatted page
|
||||
ps.endOfRecord();
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("RECORD 1 DATA"));
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 20.3 Double-Byte (DBCS) SCS & DSE Print Stream
|
||||
// ==========================================
|
||||
|
||||
@Test
|
||||
public void testDBCSSCSSpacingAndPitch() throws Exception {
|
||||
config.setCodePage("930"); // Japanese DBCS
|
||||
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
|
||||
PrintSCS3270DB scsDb = new PrintSCS3270DB(config, pd, dbcsTranslator);
|
||||
|
||||
// Setting base CPI to 12 should update DBCS CPI to 6
|
||||
scsDb.setPrintDensity(12, 6);
|
||||
assertEquals(12, scsDb.getCharsPerInch());
|
||||
assertEquals(6, scsDb.getDBCSCPI());
|
||||
|
||||
// Print 'X', SO, DBCS ideographic blank (0x4040), SI, 'Y'
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(translator.stringToEbcdic("X"));
|
||||
stream.write(PrinterConstants.SCS_SO);
|
||||
stream.write(0x40);
|
||||
stream.write(0x40);
|
||||
stream.write(PrinterConstants.SCS_SI);
|
||||
stream.write(translator.stringToEbcdic("Y"));
|
||||
|
||||
scsDb.processHostData(stream.toByteArray(), 0, stream.size());
|
||||
scsDb.flushLineBuffer();
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("X\u3000 Y")); // X at col 1, DBCS 2 cols (col 2,3), Y at col 4
|
||||
|
||||
// EOR resets DBCS mode if active
|
||||
scsDb.processSO();
|
||||
assertTrue(scsDb.isDBCSMode());
|
||||
scsDb.endOfRecord();
|
||||
assertFalse(scsDb.isDBCSMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDBCSLU3PrintStreamSOandSI() {
|
||||
config.setCodePage("930");
|
||||
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
|
||||
PrintPS3270DB psDb = new PrintPS3270DB(config, pd, dbcsTranslator);
|
||||
|
||||
ByteArrayOutputStream ds = new ByteArrayOutputStream();
|
||||
ds.write(DS3270Constants.CMD_ERASE_WRITE);
|
||||
ds.write(PrinterConstants.WCC_START_PRINT_BIT);
|
||||
|
||||
ds.write(PrinterConstants.ORDER_SBA);
|
||||
ds.write(0x40);
|
||||
ds.write(0x40);
|
||||
|
||||
// SBCS 'A'
|
||||
ds.write(0xC1);
|
||||
// SO
|
||||
ds.write(PrinterConstants.SCS_SO);
|
||||
// DBCS Kanji '日' (in Cp930: 0x43 0x81)
|
||||
ds.write(0x43);
|
||||
ds.write(0x81);
|
||||
// SI
|
||||
ds.write(PrinterConstants.SCS_SI);
|
||||
// SBCS 'Z'
|
||||
ds.write(0xE9);
|
||||
|
||||
byte[] record = ds.toByteArray();
|
||||
psDb.process3270PrintDS(record, 0, record.length);
|
||||
|
||||
// Check DBCS cells in buffer
|
||||
assertTrue(psDb.isDBCSCell(1));
|
||||
assertTrue(psDb.isDBCSLeft(1));
|
||||
assertTrue(psDb.isDBCSCell(2));
|
||||
assertTrue(psDb.isDBCSRight(2));
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertNotNull(captured);
|
||||
assertTrue(captured.contains("A"));
|
||||
assertTrue(captured.contains("Z"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user