This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package haus.nightmare.lib3270j.charset;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for Phase 8 IBM 3270 APL / Graphic Escape (GA23-0059) character set translation.
|
||||
*/
|
||||
public class APLTranslationPhase8Test {
|
||||
|
||||
@Test
|
||||
public void testAPLBoxDrawingGlyphs() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator();
|
||||
|
||||
// Box lines and corners
|
||||
assertEquals('─', translator.mapAPL(0xA2)); // Horizontal line
|
||||
assertEquals('│', translator.mapAPL(0x85)); // Vertical line
|
||||
assertEquals('┌', translator.mapAPL(0xC5)); // Top Left corner
|
||||
assertEquals('┐', translator.mapAPL(0xD5)); // Top Right corner
|
||||
assertEquals('└', translator.mapAPL(0xC4)); // Bottom Left corner
|
||||
assertEquals('┘', translator.mapAPL(0xD4)); // Bottom Right corner
|
||||
assertEquals('├', translator.mapAPL(0xC6)); // Left T
|
||||
assertEquals('┤', translator.mapAPL(0xD6)); // Right T
|
||||
assertEquals('┬', translator.mapAPL(0xC7)); // Top T
|
||||
assertEquals('┴', translator.mapAPL(0xD7)); // Bottom T
|
||||
assertEquals('┼', translator.mapAPL(0xCB)); // Cross
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAPLMathAndSpecialSymbols() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator();
|
||||
|
||||
assertEquals('≤', translator.mapAPL(0x8C)); // Less than or equal
|
||||
assertEquals('≥', translator.mapAPL(0xAE)); // Greater than or equal
|
||||
assertEquals('≠', translator.mapAPL(0xBE)); // Not equal
|
||||
assertEquals('[', translator.mapAPL(0xAD)); // Left bracket
|
||||
assertEquals(']', translator.mapAPL(0xBD)); // Right bracket
|
||||
assertEquals('{', translator.mapAPL(0x8D)); // Left brace
|
||||
assertEquals('}', translator.mapAPL(0x9D)); // Right brace
|
||||
assertEquals('°', translator.mapAPL(0xB0)); // Degree
|
||||
assertEquals('±', translator.mapAPL(0xB1)); // Plus-minus
|
||||
assertEquals('²', translator.mapAPL(0xB2)); // Superscript 2
|
||||
assertEquals('³', translator.mapAPL(0xB3)); // Superscript 3
|
||||
assertEquals('¯', translator.mapAPL(0xAF)); // Overbar / High Minus
|
||||
assertEquals('Ω', translator.mapAPL(0xBA)); // Greek Omega
|
||||
assertEquals('µ', translator.mapAPL(0xBF)); // Micro
|
||||
assertEquals('¬', translator.mapAPL(0x5F)); // Not sign
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAPLOperatorsAndStructureGlyphs() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator();
|
||||
|
||||
assertEquals('⋄', translator.mapAPL(0x80)); // Diamond
|
||||
assertEquals('⍺', translator.mapAPL(0x81)); // Alpha
|
||||
assertEquals('⊥', translator.mapAPL(0x82)); // Up tack / decode
|
||||
assertEquals('∩', translator.mapAPL(0x83)); // Intersection
|
||||
assertEquals('⌊', translator.mapAPL(0x84)); // Floor
|
||||
assertEquals('∇', translator.mapAPL(0x87)); // Del
|
||||
assertEquals('∆', translator.mapAPL(0x88)); // Delta
|
||||
assertEquals('⍳', translator.mapAPL(0x89)); // Iota
|
||||
assertEquals('→', translator.mapAPL(0x8A)); // Right arrow
|
||||
assertEquals('⍞', translator.mapAPL(0x8B)); // Quote quad
|
||||
assertEquals('×', translator.mapAPL(0x8E)); // Multiply
|
||||
assertEquals('÷', translator.mapAPL(0x8F)); // Divide
|
||||
assertEquals('⍟', translator.mapAPL(0x90)); // Circle star
|
||||
assertEquals('⌹', translator.mapAPL(0x91)); // Domino / quad divide
|
||||
assertEquals('⊤', translator.mapAPL(0x92)); // Down tack / encode
|
||||
assertEquals('∪', translator.mapAPL(0x93)); // Union
|
||||
assertEquals('⌈', translator.mapAPL(0x94)); // Ceiling
|
||||
assertEquals('⍴', translator.mapAPL(0x95)); // Rho / shape
|
||||
assertEquals('⍵', translator.mapAPL(0x96)); // Omega
|
||||
assertEquals('○', translator.mapAPL(0x99)); // Circle
|
||||
assertEquals('←', translator.mapAPL(0x9A)); // Left arrow
|
||||
assertEquals('⍋', translator.mapAPL(0x9C)); // Grade up
|
||||
assertEquals('⍒', translator.mapAPL(0x9E)); // Grade down
|
||||
assertEquals('⍝', translator.mapAPL(0x9F)); // Lamp / comment
|
||||
assertEquals('⊂', translator.mapAPL(0xA6)); // Enclose / left shoe
|
||||
assertEquals('⊃', translator.mapAPL(0xA7)); // Disclose / right shoe
|
||||
assertEquals('↑', translator.mapAPL(0xA8)); // Take / up arrow
|
||||
assertEquals('↓', translator.mapAPL(0xA9)); // Drop / down arrow
|
||||
assertEquals('⎕', translator.mapAPL(0xAA)); // Quad
|
||||
assertEquals('⍉', translator.mapAPL(0xAC)); // Transpose
|
||||
assertEquals('⊖', translator.mapAPL(0xB4)); // Circle bar
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScreenBufferAPLCharacterSetIntegration() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator();
|
||||
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
|
||||
|
||||
// Place box-drawing corner and horizontal line with cs=1 (CS_APL)
|
||||
screen.setCellWithCS(0, 0xC5, 1); // Top left ┌
|
||||
screen.setCellWithCS(1, 0xA2, 1); // Horizontal ─
|
||||
screen.setCellWithCS(2, 0xD5, 1); // Top right ┐
|
||||
|
||||
screen.translateToUnicode();
|
||||
|
||||
assertEquals('┌', (char) screen.getCell(0).ucs4);
|
||||
assertEquals('─', (char) screen.getCell(1).ucs4);
|
||||
assertEquals('┐', (char) screen.getCell(2).ucs4);
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package haus.nightmare.lib3270j.charset;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for Phase 8 custom user-defined character translation override tables.
|
||||
*/
|
||||
public class CustomTranslationOverridePhase8Test {
|
||||
|
||||
@Test
|
||||
public void testSingleOverride() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator("037");
|
||||
assertFalse(translator.hasCustomOverrides());
|
||||
|
||||
// Default CP037 mapping for 0xC1 is 'A'
|
||||
assertEquals('A', translator.ebcdicToUnicode(0xC1));
|
||||
assertEquals(0xC1, translator.unicodeToEbcdic('A'));
|
||||
|
||||
// Override 0xC1 to '★' (\u2605)
|
||||
translator.setCustomOverride(0xC1, '★');
|
||||
assertTrue(translator.hasCustomOverrides());
|
||||
assertEquals('★', translator.ebcdicToUnicode(0xC1));
|
||||
assertEquals(0xC1, translator.unicodeToEbcdic('★'));
|
||||
|
||||
// Non-overridden characters should continue using default CP037
|
||||
assertEquals('B', translator.ebcdicToUnicode(0xC2));
|
||||
assertEquals(0xC2, translator.unicodeToEbcdic('B'));
|
||||
|
||||
// Safe conversion
|
||||
assertEquals((byte) 0xC1, translator.unicodeToEbcdicSafe('★'));
|
||||
|
||||
// Remove override
|
||||
translator.removeCustomOverride(0xC1);
|
||||
assertFalse(translator.hasCustomOverrides());
|
||||
assertEquals('A', translator.ebcdicToUnicode(0xC1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkOverrides() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator("037");
|
||||
|
||||
Map<Integer, Character> ebcToUni = new HashMap<>();
|
||||
ebcToUni.put(0x81, 'α'); // replace 'a' with alpha
|
||||
ebcToUni.put(0x82, 'β'); // replace 'b' with beta
|
||||
|
||||
translator.setCustomOverrides(ebcToUni, null);
|
||||
assertTrue(translator.hasCustomOverrides());
|
||||
|
||||
assertEquals('α', translator.ebcdicToUnicode(0x81));
|
||||
assertEquals('β', translator.ebcdicToUnicode(0x82));
|
||||
assertEquals('c', translator.ebcdicToUnicode(0x83));
|
||||
|
||||
assertEquals(0x81, translator.unicodeToEbcdic('α'));
|
||||
assertEquals(0x82, translator.unicodeToEbcdic('β'));
|
||||
|
||||
// Clear overrides
|
||||
translator.clearCustomOverrides();
|
||||
assertFalse(translator.hasCustomOverrides());
|
||||
assertEquals('a', translator.ebcdicToUnicode(0x81));
|
||||
assertEquals('b', translator.ebcdicToUnicode(0x82));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringTranslationWithOverrides() {
|
||||
EbcdicTranslator translator = new EbcdicTranslator("037");
|
||||
translator.setCustomOverride(0xBA, '❮'); // override '[' with '❮'
|
||||
translator.setCustomOverride(0xBB, '❯'); // override ']' with '❯'
|
||||
|
||||
byte[] input = new byte[] { (byte) 0xBA, (byte) 0xC8, (byte) 0xC9, (byte) 0xBB }; // [HI] in CP037
|
||||
String decoded = translator.ebcdicToString(input, 0, input.length);
|
||||
assertEquals("❮HI❯", decoded);
|
||||
|
||||
byte[] reEncoded = translator.stringToEbcdic("❮HI❯");
|
||||
assertArrayEquals(input, reEncoded);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package haus.nightmare.lib3270j.charset;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for Phase 8 DBCS SO/SI escape sequence handling, shift preservation, and UTF-8 conversion.
|
||||
*/
|
||||
public class DBCSSOSIEscapingPhase8Test {
|
||||
|
||||
@Test
|
||||
public void testSOSIPreserveMode() {
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) CodePageRegistry.getCodePage("930");
|
||||
cp.registerDbcsPair(0x4341, '\u6771'); // '東'
|
||||
cp.registerDbcsPair(0x4342, '\u4EAC'); // '京'
|
||||
|
||||
// EBCDIC: 'I'(0xC9) + SO(0x0E) + 東(0x4341) + 京(0x4342) + SI(0x0F) + '1'(0xF1)
|
||||
byte[] ebcdic = new byte[] {
|
||||
(byte) 0xC9,
|
||||
0x0E, (byte) 0x43, (byte) 0x41, (byte) 0x43, (byte) 0x42, 0x0F,
|
||||
(byte) 0xF1
|
||||
};
|
||||
|
||||
// Standard mode strips SO/SI from Unicode string
|
||||
String standard = cp.ebcdicToString(ebcdic, 0, ebcdic.length, false);
|
||||
assertEquals("I東京1", standard);
|
||||
|
||||
// Preserved mode keeps \u000E and \u000F in Unicode string
|
||||
String preserved = cp.ebcdicToString(ebcdic, 0, ebcdic.length, true);
|
||||
assertEquals("I\u000E東京\u000F1", preserved);
|
||||
|
||||
// Utility helper
|
||||
String utf8WithSosi = cp.ebcdicToUtf8WithSOSI(ebcdic);
|
||||
assertEquals(preserved, utf8WithSosi);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmbeddedSOSIStringConversion() {
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) CodePageRegistry.getCodePage("930");
|
||||
cp.registerDbcsPair(0x4341, '\u6771'); // '東'
|
||||
cp.registerDbcsPair(0x4342, '\u4EAC'); // '京'
|
||||
|
||||
// Encode string that already contains embedded \u000E and \u000F
|
||||
String inputWithSOSI = "I\u000E東京\u000F1";
|
||||
byte[] encoded = cp.stringToEbcdic(inputWithSOSI, true);
|
||||
|
||||
byte[] expected = new byte[] {
|
||||
(byte) 0xC9,
|
||||
0x0E, (byte) 0x43, (byte) 0x41, (byte) 0x43, (byte) 0x42, 0x0F,
|
||||
(byte) 0xF1
|
||||
};
|
||||
assertArrayEquals(expected, encoded);
|
||||
|
||||
// Utility helper
|
||||
byte[] helperEncoded = cp.utf8WithSOSIToEbcdic(inputWithSOSI);
|
||||
assertArrayEquals(expected, helperEncoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCloseUnclosedDBCSShift() {
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) CodePageRegistry.getCodePage("930");
|
||||
cp.registerDbcsPair(0x4341, '\u6771'); // '東'
|
||||
|
||||
// Text ending with a DBCS character without explicit SI
|
||||
String s = "A東京";
|
||||
byte[] encoded = cp.stringToEbcdic(s, false);
|
||||
|
||||
// Must end with SI (0x0F)
|
||||
assertTrue(encoded.length > 0);
|
||||
assertEquals(0x0F, encoded[encoded.length - 1] & 0xFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedundantSOSISuppression() {
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) CodePageRegistry.getCodePage("930");
|
||||
cp.registerDbcsPair(0x4341, '\u6771');
|
||||
|
||||
// Input containing redundant duplicate \u000E \u000E
|
||||
String s = "\u000E\u000E東\u000F\u000F";
|
||||
byte[] encoded = cp.stringToEbcdic(s, true);
|
||||
|
||||
// Should contain only one SO (0x0E) and one SI (0x0F)
|
||||
assertEquals(4, encoded.length);
|
||||
assertEquals(0x0E, encoded[0] & 0xFF);
|
||||
assertEquals(0x43, encoded[1] & 0xFF);
|
||||
assertEquals(0x41, encoded[2] & 0xFF);
|
||||
assertEquals(0x0F, encoded[3] & 0xFF);
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package haus.nightmare.lib3270j.charset;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for Phase 8 Extended EBCDIC Codepages (Arabic, Hebrew, Thai, Cyrillic, Turkish, Extended DBCS).
|
||||
*/
|
||||
public class ExtendedCodePagesPhase8Test {
|
||||
|
||||
@Test
|
||||
public void testArabicCp420() {
|
||||
CodePage cp = CodePageRegistry.getCodePage("420");
|
||||
assertNotNull(cp);
|
||||
assertEquals("420", cp.getCodePageId());
|
||||
assertEquals(420, cp.getCpgid());
|
||||
assertFalse(cp.isDBCS());
|
||||
|
||||
// Test alias resolution
|
||||
assertEquals("420", CodePageRegistry.getCodePage("ar").getCodePageId());
|
||||
assertEquals("420", CodePageRegistry.getCodePage("arabic").getCodePageId());
|
||||
assertEquals("420", CodePageRegistry.getCodePage("ebcdic-cp-ar").getCodePageId());
|
||||
assertEquals("420", CodePageRegistry.getCodePage("IBM420").getCodePageId());
|
||||
|
||||
// Basic ASCII and Arabic character mapping
|
||||
assertEquals('A', cp.ebcdicToUnicode(0xC1));
|
||||
assertEquals(0xC1, cp.unicodeToEbcdic('A'));
|
||||
assertEquals(' ', cp.ebcdicToUnicode(0x40));
|
||||
assertEquals(0x40, cp.unicodeToEbcdic(' '));
|
||||
|
||||
// Arabic characters (Hamza, Alef, Yeh, etc.)
|
||||
int ebcHamza = cp.unicodeToEbcdic('\u0621');
|
||||
assertTrue(ebcHamza >= 0);
|
||||
assertEquals('\u0621', cp.ebcdicToUnicode(ebcHamza));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHebrewCp424AndCp803() {
|
||||
CodePage cp424 = CodePageRegistry.getCodePage("424");
|
||||
assertNotNull(cp424);
|
||||
assertEquals("424", cp424.getCodePageId());
|
||||
assertEquals(424, cp424.getCpgid());
|
||||
assertEquals(941, cp424.getCgcsgid());
|
||||
|
||||
assertEquals("424", CodePageRegistry.getCodePage("he").getCodePageId());
|
||||
assertEquals("424", CodePageRegistry.getCodePage("hebrew").getCodePageId());
|
||||
|
||||
// Hebrew Alef \u05D0 in Cp424
|
||||
int ebcAlef424 = cp424.unicodeToEbcdic('\u05D0');
|
||||
assertTrue(ebcAlef424 >= 0);
|
||||
assertEquals('\u05D0', cp424.ebcdicToUnicode(ebcAlef424));
|
||||
|
||||
CodePage cp803 = CodePageRegistry.getCodePage("803");
|
||||
assertNotNull(cp803);
|
||||
assertEquals("803", cp803.getCodePageId());
|
||||
assertEquals(803, cp803.getCpgid());
|
||||
assertEquals(1147, cp803.getCgcsgid());
|
||||
|
||||
assertEquals("803", CodePageRegistry.getCodePage("hebrew-old").getCodePageId());
|
||||
assertEquals("803", CodePageRegistry.getCodePage("israel").getCodePageId());
|
||||
assertEquals("803", CodePageRegistry.getCodePage("iw").getCodePageId());
|
||||
|
||||
// Hebrew Alef in Cp803
|
||||
int ebcAlef803 = cp803.unicodeToEbcdic('\u05D0');
|
||||
assertTrue(ebcAlef803 >= 0);
|
||||
assertEquals('\u05D0', cp803.ebcdicToUnicode(ebcAlef803));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThaiCp838AndCp1160() {
|
||||
CodePage cp838 = CodePageRegistry.getCodePage("838");
|
||||
assertNotNull(cp838);
|
||||
assertEquals("838", cp838.getCodePageId());
|
||||
assertEquals(838, cp838.getCpgid());
|
||||
assertEquals(1176, cp838.getCgcsgid());
|
||||
|
||||
assertEquals("838", CodePageRegistry.getCodePage("th").getCodePageId());
|
||||
assertEquals("838", CodePageRegistry.getCodePage("thai").getCodePageId());
|
||||
|
||||
// Thai character Ko Kai \u0E01
|
||||
int ebcKoKai = cp838.unicodeToEbcdic('\u0E01');
|
||||
assertTrue(ebcKoKai >= 0);
|
||||
assertEquals('\u0E01', cp838.ebcdicToUnicode(ebcKoKai));
|
||||
|
||||
CodePage cp1160 = CodePageRegistry.getCodePage("1160");
|
||||
assertNotNull(cp1160);
|
||||
assertEquals("1160", cp1160.getCodePageId());
|
||||
assertEquals(1160, cp1160.getCpgid());
|
||||
assertEquals('€', cp1160.ebcdicToUnicode(0x9F));
|
||||
assertEquals(0x9F, cp1160.unicodeToEbcdic('€'));
|
||||
assertEquals("1160", CodePageRegistry.getCodePage("thai-euro").getCodePageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCyrillicCp1025Cp1123Cp1154Cp880() {
|
||||
CodePage cp1025 = CodePageRegistry.getCodePage("1025");
|
||||
assertNotNull(cp1025);
|
||||
assertEquals(1025, cp1025.getCpgid());
|
||||
assertEquals(1150, cp1025.getCgcsgid());
|
||||
assertEquals("1025", CodePageRegistry.getCodePage("ru").getCodePageId());
|
||||
assertEquals("1025", CodePageRegistry.getCodePage("cyrillic").getCodePageId());
|
||||
assertEquals("1025", CodePageRegistry.getCodePage("russian").getCodePageId());
|
||||
|
||||
// Cyrillic Capital Letter A \u0410 and small a \u0430
|
||||
int ebcA1025 = cp1025.unicodeToEbcdic('\u0410');
|
||||
assertTrue(ebcA1025 >= 0);
|
||||
assertEquals('\u0410', cp1025.ebcdicToUnicode(ebcA1025));
|
||||
|
||||
// Cp1123 Ukraine
|
||||
CodePage cp1123 = CodePageRegistry.getCodePage("1123");
|
||||
assertNotNull(cp1123);
|
||||
assertEquals(1123, cp1123.getCpgid());
|
||||
assertEquals(1399, cp1123.getCgcsgid());
|
||||
assertEquals("1123", CodePageRegistry.getCodePage("ukraine").getCodePageId());
|
||||
assertEquals("1123", CodePageRegistry.getCodePage("ukrainian").getCodePageId());
|
||||
|
||||
// Cp1154 Cyrillic with Euro
|
||||
CodePage cp1154 = CodePageRegistry.getCodePage("1154");
|
||||
assertNotNull(cp1154);
|
||||
assertEquals(1154, cp1154.getCpgid());
|
||||
assertEquals(1305, cp1154.getCgcsgid());
|
||||
assertEquals('€', cp1154.ebcdicToUnicode(0x9F));
|
||||
assertEquals("1154", CodePageRegistry.getCodePage("cyrillic-euro").getCodePageId());
|
||||
|
||||
// Cp880 Cyrillic Russian
|
||||
CodePage cp880 = CodePageRegistry.getCodePage("880");
|
||||
assertNotNull(cp880);
|
||||
assertEquals(880, cp880.getCpgid());
|
||||
assertEquals(960, cp880.getCgcsgid());
|
||||
assertEquals("880", CodePageRegistry.getCodePage("cyrillic-russian").getCodePageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTurkishCp1155AndCp905() {
|
||||
CodePage cp1155 = CodePageRegistry.getCodePage("1155");
|
||||
assertNotNull(cp1155);
|
||||
assertEquals(1155, cp1155.getCpgid());
|
||||
assertEquals(1306, cp1155.getCgcsgid());
|
||||
assertEquals('€', cp1155.ebcdicToUnicode(0x9F));
|
||||
assertEquals("1155", CodePageRegistry.getCodePage("turkish-euro").getCodePageId());
|
||||
|
||||
CodePage cp905 = CodePageRegistry.getCodePage("905");
|
||||
assertNotNull(cp905);
|
||||
assertEquals(905, cp905.getCpgid());
|
||||
assertEquals(1151, cp905.getCgcsgid());
|
||||
assertEquals("905", CodePageRegistry.getCodePage("turkish-latin3").getCodePageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedChineseDBCSCp1388AndCp1371() {
|
||||
CodePage cp1388 = CodePageRegistry.getCodePage("1388");
|
||||
assertNotNull(cp1388);
|
||||
assertTrue(cp1388.isDBCS());
|
||||
assertEquals(1388, cp1388.getCpgid());
|
||||
assertEquals(1175, cp1388.getCgcsgid());
|
||||
assertEquals("1388", CodePageRegistry.getCodePage("chinese-ext-simplified").getCodePageId());
|
||||
assertEquals("1388", CodePageRegistry.getCodePage("zh-simplified-ext").getCodePageId());
|
||||
|
||||
CodePage cp1371 = CodePageRegistry.getCodePage("1371");
|
||||
assertNotNull(cp1371);
|
||||
assertTrue(cp1371.isDBCS());
|
||||
assertEquals(1371, cp1371.getCpgid());
|
||||
assertEquals(1174, cp1371.getCgcsgid());
|
||||
assertEquals("1371", CodePageRegistry.getCodePage("chinese-ext-traditional").getCodePageId());
|
||||
assertEquals("1371", CodePageRegistry.getCodePage("zh-traditional-ext").getCodePageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasCodePageQuery() {
|
||||
assertTrue(CodePageRegistry.hasCodePage("037"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1047"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("420"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("424"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("803"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("838"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1160"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1025"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1123"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1154"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("880"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1155"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("905"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1388"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("1371"));
|
||||
|
||||
assertTrue(CodePageRegistry.hasCodePage("ar"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("hebrew"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("thai"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("russian"));
|
||||
assertTrue(CodePageRegistry.hasCodePage("ukraine"));
|
||||
|
||||
assertFalse(CodePageRegistry.hasCodePage("nonexistent-cp-9999"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
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 java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Comprehensive Unit Tests for Phase 10: ECL API & Session Architecture Facade.
|
||||
*/
|
||||
public class ECLSessionPhase10Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLPS ps;
|
||||
private ECLOIA oia;
|
||||
private Telnet3270Client client;
|
||||
private ECLSession session;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ConnectionConfig config = new ConnectionConfig("mainframe.example.com", 23, TerminalModel.IBM_3279_4);
|
||||
config.setLuName("LU3270A");
|
||||
config.setCodePage("037");
|
||||
|
||||
client = new Telnet3270Client(config);
|
||||
session = new ECLSession(client);
|
||||
ps = session.GetPS();
|
||||
oia = session.GetOIA();
|
||||
screen = client.getScreenBuffer();
|
||||
translator = client.getTranslator();
|
||||
input = client.getInputProcessor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLSessionInstantiationWithConfigAndProperties() {
|
||||
// Test default constructor
|
||||
ECLSession defSession = new ECLSession();
|
||||
assertNotNull(defSession.GetPS());
|
||||
assertNotNull(defSession.GetOIA());
|
||||
assertNotNull(defSession.GetConnection());
|
||||
assertNotNull(defSession.GetXfer());
|
||||
assertNotNull(defSession.GetFieldList());
|
||||
assertNotNull(defSession.GetClient());
|
||||
assertEquals(23, defSession.GetConnection().GetPort());
|
||||
|
||||
// Test explicit host/port/model constructor
|
||||
ECLSession hpSession = new ECLSession("tso.test.org", 992, TerminalModel.IBM_3279_2, true);
|
||||
assertEquals("tso.test.org", hpSession.GetConnection().GetHost());
|
||||
assertEquals(992, hpSession.GetConnection().GetPort());
|
||||
assertEquals(TerminalModel.IBM_3279_2, hpSession.GetConnection().GetModel());
|
||||
assertTrue(hpSession.GetConnection().IsSSL());
|
||||
|
||||
// Test Properties constructor
|
||||
Properties props = new Properties();
|
||||
props.setProperty(ECLSession.SESSION_HOST, "mvs.corp.net");
|
||||
props.setProperty(ECLSession.SESSION_PORT, "2023");
|
||||
props.setProperty(ECLSession.SESSION_CODE_PAGE, "1047");
|
||||
props.setProperty(ECLSession.SESSION_MODEL, "5"); // 27x132
|
||||
props.setProperty(ECLSession.SESSION_SSL, "true");
|
||||
props.setProperty(ECLSession.SESSION_LU_NAME, "TSOLU01");
|
||||
props.setProperty(ECLSession.SESSION_TN3270E, "true");
|
||||
|
||||
ECLSession propSession = new ECLSession(props);
|
||||
assertEquals("mvs.corp.net", propSession.GetConnection().GetHost());
|
||||
assertEquals(2023, propSession.GetConnection().GetPort());
|
||||
assertEquals("1047", propSession.GetConnection().GetCodePage());
|
||||
assertEquals(TerminalModel.IBM_3279_5, propSession.GetConnection().GetModel());
|
||||
assertTrue(propSession.GetConnection().IsSSL());
|
||||
assertEquals("TSOLU01", propSession.GetConnection().GetLUName());
|
||||
assertEquals("TN3270E", propSession.GetConnection().GetConnType());
|
||||
|
||||
// Verify Properties sync and retrieval
|
||||
Properties retrieved = propSession.GetProperties();
|
||||
assertEquals("mvs.corp.net", retrieved.getProperty(ECLSession.SESSION_HOST));
|
||||
assertEquals("2023", retrieved.getProperty(ECLSession.SESSION_PORT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLConnectionAttributesAndState() {
|
||||
ECLConnection conn = session.GetConnection();
|
||||
assertNotNull(conn);
|
||||
assertEquals("mainframe.example.com", conn.GetHost());
|
||||
assertEquals(23, conn.GetPort());
|
||||
assertEquals("037", conn.GetCodePage());
|
||||
assertEquals(TerminalModel.IBM_3279_4, conn.GetModel());
|
||||
assertEquals("LU3270A", conn.GetLUName());
|
||||
assertEquals("LU3270A", conn.GetDevName());
|
||||
assertFalse(conn.IsSSL());
|
||||
assertEquals(ConnectionState.NOT_CONNECTED, conn.GetState());
|
||||
assertEquals(0, conn.GetStateCode());
|
||||
assertFalse(conn.IsConnected());
|
||||
assertFalse(conn.IsReady());
|
||||
assertTrue(conn.IsDisconnecting());
|
||||
|
||||
// Verify state mappings
|
||||
assertEquals(0, ConnectionState.NOT_CONNECTED.toHoDStateCode());
|
||||
assertEquals(1, ConnectionState.TELNET_PENDING.toHoDStateCode());
|
||||
assertEquals(2, ConnectionState.CONNECTED_NVT.toHoDStateCode());
|
||||
assertEquals(2, ConnectionState.CONNECTED_UNBOUND.toHoDStateCode());
|
||||
assertEquals(3, ConnectionState.CONNECTED_3270.toHoDStateCode());
|
||||
assertEquals(3, ConnectionState.CONNECTED_TN3270E.toHoDStateCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLScreenDescMatching() {
|
||||
// Setup screen buffer with sample formatted screen
|
||||
screen.clear();
|
||||
screen.setCursorAddress(0);
|
||||
// Write: SF (prot), "TSO/E LOGON", SF (unprot), "USER01", SF (prot), "PASSWORD", SF (unprot), " "
|
||||
screen.setCellFA(0, (byte) 0x60); // SF protected
|
||||
String text1 = "TSO/E LOGON";
|
||||
for (int i = 0; i < text1.length(); i++) {
|
||||
screen.setChar(0, i + 1, text1.charAt(i));
|
||||
}
|
||||
|
||||
int userFaPos = 20;
|
||||
screen.setCellFA(userFaPos, (byte) 0x40); // SF unprotected
|
||||
String userVal = "USER01";
|
||||
for (int i = 0; i < userVal.length(); i++) {
|
||||
screen.setChar(0, userFaPos + 1 + i, userVal.charAt(i));
|
||||
}
|
||||
|
||||
int passFaPos = 40;
|
||||
screen.setCellFA(passFaPos, (byte) 0x60); // SF protected
|
||||
String passLabel = "PASSWORD";
|
||||
for (int i = 0; i < passLabel.length(); i++) {
|
||||
screen.setChar(0, passFaPos + 1 + i, passLabel.charAt(i));
|
||||
}
|
||||
|
||||
int passInputPos = 60;
|
||||
screen.setCellFA(passInputPos, (byte) 0x40); // SF unprot hidden
|
||||
|
||||
ps.setCursorPos(userFaPos + 1);
|
||||
|
||||
// 1. Test match string at 1-based row/col
|
||||
ECLScreenDesc desc1 = new ECLScreenDesc();
|
||||
desc1.AddString("TSO/E LOGON", 1, 2, true);
|
||||
assertTrue(desc1.Matches(session));
|
||||
assertTrue(desc1.Matches(ps, oia));
|
||||
assertTrue(desc1.Matches(client));
|
||||
|
||||
// Test case sensitivity
|
||||
ECLScreenDesc descCaseMismatch = new ECLScreenDesc();
|
||||
descCaseMismatch.AddString("tso/e logon", 1, 2, true);
|
||||
assertFalse(descCaseMismatch.Matches(session));
|
||||
|
||||
ECLScreenDesc descCaseInsensitive = new ECLScreenDesc();
|
||||
descCaseInsensitive.AddString("tso/e logon", 1, 2, false);
|
||||
assertTrue(descCaseInsensitive.Matches(session));
|
||||
|
||||
// 2. Test match string anywhere on screen
|
||||
ECLScreenDesc descAnywhere = new ECLScreenDesc();
|
||||
descAnywhere.AddString("PASSWORD");
|
||||
assertTrue(descAnywhere.Matches(session));
|
||||
|
||||
ECLScreenDesc descAnywhereFail = new ECLScreenDesc();
|
||||
descAnywhereFail.AddString("CICS TRANSACTION");
|
||||
assertFalse(descAnywhereFail.Matches(session));
|
||||
|
||||
// 3. Test match string in rectangular region
|
||||
ECLScreenDesc descRect = new ECLScreenDesc();
|
||||
descRect.AddStringInRect("USER01", 1, 1, 2, 80, true);
|
||||
assertTrue(descRect.Matches(session));
|
||||
|
||||
ECLScreenDesc descRectFail = new ECLScreenDesc();
|
||||
descRectFail.AddStringInRect("USER01", 2, 1, 5, 80, true);
|
||||
assertFalse(descRectFail.Matches(session));
|
||||
|
||||
// 4. Test match cursor position
|
||||
ECLScreenDesc descCursor = new ECLScreenDesc();
|
||||
descCursor.AddCursorPos(1, userFaPos + 2);
|
||||
assertTrue(descCursor.Matches(session));
|
||||
|
||||
ECLScreenDesc descCursorFail = new ECLScreenDesc();
|
||||
descCursorFail.AddCursorPos(10, 10);
|
||||
assertFalse(descCursorFail.Matches(session));
|
||||
|
||||
// 5. Test field counts
|
||||
ECLScreenDesc descFields = new ECLScreenDesc();
|
||||
descFields.AddNumFields(4);
|
||||
descFields.AddNumInputFields(2);
|
||||
assertTrue(descFields.Matches(session));
|
||||
|
||||
ECLScreenDesc descFieldsFail = new ECLScreenDesc();
|
||||
descFieldsFail.AddNumFields(10);
|
||||
assertFalse(descFieldsFail.Matches(session));
|
||||
|
||||
// 6. Test OIA status
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
ECLScreenDesc descOia = new ECLScreenDesc();
|
||||
descOia.AddOIAStatus(ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
assertTrue(descOia.Matches(session));
|
||||
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_SYSTEM_LOCK);
|
||||
assertFalse(descOia.Matches(session));
|
||||
|
||||
ECLScreenDesc descOiaLocked = new ECLScreenDesc();
|
||||
descOiaLocked.AddOIAStatus(ECLConstants.INHIBIT_SYSTEM_LOCK);
|
||||
assertTrue(descOiaLocked.Matches(session));
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
|
||||
// 7. Test Clear and composite descriptor
|
||||
ECLScreenDesc composite = new ECLScreenDesc();
|
||||
composite.AddString("TSO/E LOGON", 1, 2);
|
||||
composite.AddString("PASSWORD");
|
||||
composite.AddNumFields(4);
|
||||
assertEquals(3, composite.getConditionCount());
|
||||
assertTrue(composite.Matches(session));
|
||||
|
||||
composite.Clear();
|
||||
assertEquals(0, composite.getConditionCount());
|
||||
assertTrue(composite.Matches(session)); // Empty descriptor matches everything
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLPSListenerAndEventDispatching() {
|
||||
AtomicInteger updateCount = new AtomicInteger(0);
|
||||
AtomicInteger cursorCount = new AtomicInteger(0);
|
||||
AtomicInteger alarmCount = new AtomicInteger(0);
|
||||
AtomicInteger resizeCount = new AtomicInteger(0);
|
||||
AtomicReference<ECLPSEvent> lastEvent = new AtomicReference<>();
|
||||
|
||||
ECLPSListener listener = new ECLPSListener() {
|
||||
@Override
|
||||
public void psChanged(ECLPSEvent event) {
|
||||
updateCount.incrementAndGet();
|
||||
lastEvent.set(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void psCursorMoved(ECLPSEvent event) {
|
||||
cursorCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void psAlarm(ECLPSEvent event) {
|
||||
alarmCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void psResized(ECLPSEvent event) {
|
||||
resizeCount.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
ps.RegisterPSEvent(listener);
|
||||
|
||||
// 1. Trigger PS Update
|
||||
ps.notifyPSUpdate(2, 5, 2, 20, false);
|
||||
assertEquals(1, updateCount.get());
|
||||
assertNotNull(lastEvent.get());
|
||||
assertEquals(ECLPSEvent.PS_UPDATE, lastEvent.get().getEventType());
|
||||
assertEquals(2, lastEvent.get().getStartRow());
|
||||
assertEquals(5, lastEvent.get().getStartCol());
|
||||
assertEquals(2, lastEvent.get().getEndRow());
|
||||
assertEquals(20, lastEvent.get().getEndCol());
|
||||
assertFalse(lastEvent.get().isFullUpdate());
|
||||
assertSame(ps, lastEvent.get().getPS());
|
||||
|
||||
// 2. Trigger Cursor Move
|
||||
ps.notifyCursorMoved(0, 85);
|
||||
assertEquals(2, updateCount.get());
|
||||
assertEquals(1, cursorCount.get());
|
||||
assertEquals(ECLPSEvent.PS_CURSOR, lastEvent.get().getEventType());
|
||||
assertEquals(85, lastEvent.get().getNewCursorAddress());
|
||||
|
||||
// 3. Trigger Alarm
|
||||
ps.notifyAlarm();
|
||||
assertEquals(3, updateCount.get());
|
||||
assertEquals(1, alarmCount.get());
|
||||
assertEquals(ECLPSEvent.PS_ALARM, lastEvent.get().getEventType());
|
||||
|
||||
// 4. Trigger Screen Resize
|
||||
ps.notifyScreenResized(32, 80);
|
||||
assertEquals(4, updateCount.get());
|
||||
assertEquals(1, resizeCount.get());
|
||||
assertEquals(ECLPSEvent.PS_RESIZE, lastEvent.get().getEventType());
|
||||
assertEquals(32, lastEvent.get().getRows());
|
||||
assertEquals(80, lastEvent.get().getCols());
|
||||
|
||||
// 5. Unregister listener
|
||||
ps.UnregisterPSEvent(listener);
|
||||
ps.notifyPSUpdate(0, 0, 23, 79, true);
|
||||
assertEquals(4, updateCount.get()); // Count should not change after unregister
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLOIAListenerAndEventDispatching() {
|
||||
AtomicInteger changeCount = new AtomicInteger(0);
|
||||
AtomicInteger lockCount = new AtomicInteger(0);
|
||||
AtomicReference<ECLOIAEvent> lastOiaEvent = new AtomicReference<>();
|
||||
|
||||
ECLOIAListener listener = new ECLOIAListener() {
|
||||
@Override
|
||||
public void oiaChanged(ECLOIAEvent event) {
|
||||
changeCount.incrementAndGet();
|
||||
lastOiaEvent.set(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void oiaLockStateChanged(ECLOIAEvent event) {
|
||||
lockCount.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
oia.RegisterOIAEvent(listener);
|
||||
|
||||
// 1. Change inhibit state
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_PROTECTED_FIELD);
|
||||
assertEquals(1, changeCount.get());
|
||||
assertNotNull(lastOiaEvent.get());
|
||||
assertEquals(ECLConstants.INHIBIT_PROTECTED_FIELD, lastOiaEvent.get().getInputInhibited());
|
||||
assertEquals("X-PROT", lastOiaEvent.get().getStatusString());
|
||||
assertTrue(lastOiaEvent.get().isInputInhibited());
|
||||
assertSame(oia, lastOiaEvent.get().getOIA());
|
||||
|
||||
// 2. Change inhibit back to normal
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
assertEquals(2, changeCount.get());
|
||||
assertEquals(ECLConstants.INHIBIT_NOT_INHIBITED, lastOiaEvent.get().getInputInhibited());
|
||||
assertEquals("READY", lastOiaEvent.get().getStatusString());
|
||||
assertFalse(lastOiaEvent.get().isInputInhibited());
|
||||
|
||||
// 3. Test uppercase wait methods
|
||||
assertTrue(oia.WaitForInput(100));
|
||||
assertTrue(oia.WaitForSystemAvailable(100));
|
||||
assertTrue(oia.WaitForAppAvailable(100));
|
||||
|
||||
// 4. Unregister listener
|
||||
oia.UnregisterOIAEvent(listener);
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_NUMERIC_ONLY);
|
||||
assertEquals(2, changeCount.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECLCommListenerAndEventDispatching() {
|
||||
ECLConnection conn = session.GetConnection();
|
||||
AtomicInteger commCount = new AtomicInteger(0);
|
||||
AtomicInteger errorCount = new AtomicInteger(0);
|
||||
AtomicBoolean notifyConnected = new AtomicBoolean(false);
|
||||
AtomicReference<ECLCommEvent> lastCommEvent = new AtomicReference<>();
|
||||
|
||||
ECLCommListener listener = new ECLCommListener() {
|
||||
@Override
|
||||
public void commEvent(ECLCommEvent event) {
|
||||
commCount.incrementAndGet();
|
||||
lastCommEvent.set(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commError(ECLCommEvent event) {
|
||||
errorCount.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
conn.RegisterCommEvent(listener);
|
||||
conn.RegisterCommEvent(connected -> notifyConnected.set(connected), false);
|
||||
|
||||
// Simulate connection state change via listener
|
||||
client.getTelnetFSM().onError("Connection reset by peer");
|
||||
assertEquals(1, commCount.get());
|
||||
assertEquals(1, errorCount.get());
|
||||
assertNotNull(lastCommEvent.get());
|
||||
assertEquals(ECLCommEvent.COMM_ERROR, lastCommEvent.get().getEventType());
|
||||
assertEquals("Connection reset by peer", lastCommEvent.get().getErrorMessage());
|
||||
|
||||
// Unregister
|
||||
conn.UnregisterCommEvent(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendKeysWithCoordinates() {
|
||||
screen.clear();
|
||||
screen.setCursorAddress(0);
|
||||
// Setup an unprotected field at row 5, col 10 (0-based: row 4, col 9)
|
||||
int faPos = 4 * 80 + 8; // 328
|
||||
screen.setCellFA(faPos, (byte) 0x40); // Unprotected
|
||||
|
||||
// Send keys with 1-based (row 5, col 10)
|
||||
session.SendKeys("IBM3270", 5, 10);
|
||||
assertEquals("IBM3270", ps.getString(faPos + 1, 7));
|
||||
|
||||
// Send bracketed mnemonics
|
||||
ps.setCursorPos(1, 1);
|
||||
ps.SendKeys("A[tab]B", 1, 1);
|
||||
assertNotNull(ps.getString(0, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaitForScreenWithDescriptor() {
|
||||
screen.clear();
|
||||
screen.setChar(0, 0, 'L');
|
||||
screen.setChar(0, 1, 'O');
|
||||
screen.setChar(0, 2, 'G');
|
||||
screen.setChar(0, 3, 'O');
|
||||
screen.setChar(0, 4, 'N');
|
||||
|
||||
ECLScreenDesc desc = new ECLScreenDesc();
|
||||
desc.AddString("LOGON", 1, 1);
|
||||
|
||||
assertTrue(session.WaitForScreen(desc, 100));
|
||||
assertTrue(ps.WaitForScreen(desc, 100));
|
||||
|
||||
ECLScreenDesc descFail = new ECLScreenDesc();
|
||||
descFail.AddString("NONEXISTENT", 1, 1);
|
||||
assertFalse(session.WaitForScreen(descFail, 50));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIBMHoDPackageCompatibility() {
|
||||
// Instantiate using haus.nightmare.lib3270j.eNetwork.ECL.* package hierarchy
|
||||
haus.nightmare.lib3270j.eNetwork.ECL.ECLSession hodSession = new haus.nightmare.lib3270j.eNetwork.ECL.ECLSession();
|
||||
assertNotNull(hodSession.GetPS());
|
||||
assertNotNull(hodSession.GetOIA());
|
||||
assertNotNull(hodSession.GetConnection());
|
||||
assertNotNull(hodSession.GetXfer());
|
||||
assertNotNull(hodSession.GetFieldList());
|
||||
|
||||
haus.nightmare.lib3270j.eNetwork.ECL.ECLScreenDesc hodDesc = new haus.nightmare.lib3270j.eNetwork.ECL.ECLScreenDesc();
|
||||
hodDesc.AddString("TEST");
|
||||
assertEquals(1, hodDesc.getConditionCount());
|
||||
|
||||
AtomicBoolean hodPsReceived = new AtomicBoolean(false);
|
||||
hodSession.GetPS().RegisterPSEvent(new haus.nightmare.lib3270j.eNetwork.ECL.event.ECLPSListener() {
|
||||
@Override
|
||||
public void psChanged(haus.nightmare.lib3270j.ecl.ECLPSEvent event) {
|
||||
hodPsReceived.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
hodSession.GetPS().notifyPSUpdate(0, 0, 10, 10, true);
|
||||
assertTrue(hodPsReceived.get());
|
||||
|
||||
// Test exception
|
||||
haus.nightmare.lib3270j.eNetwork.ECL.ECLException ex = new haus.nightmare.lib3270j.eNetwork.ECL.ECLException(haus.nightmare.lib3270j.eNetwork.ECL.ECLErrors.ECL_ERR_COMM_TIMEOUT, "Timeout");
|
||||
assertEquals(haus.nightmare.lib3270j.eNetwork.ECL.ECLErrors.ECL_ERR_COMM_TIMEOUT, ex.getErrorCode());
|
||||
assertEquals("Timeout", ex.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -88,4 +88,51 @@ public class ECLXferTest {
|
||||
assertTrue(loaded[0]);
|
||||
assertEquals(1, fileList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFilesBatchDownloadCallback() {
|
||||
boolean[] loaded = new boolean[1];
|
||||
xfer.getFiles("PROFILE EXEC A1 V 80 25 1 2026-05-10 14:22:01\n", "/tmp/nonexistent_test_dir", new haus.nightmare.lib3270j.ft.dir.FileTransferHostDirectoryInterface() {
|
||||
@Override
|
||||
public void onDirectoryLoaded(List<? extends HostDirectoryEntry> entries) {
|
||||
loaded[0] = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDirectoryError(String errorMessage) {}
|
||||
});
|
||||
|
||||
assertTrue(loaded[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFilesVectorOverload() {
|
||||
java.util.Vector<String> localFiles = new java.util.Vector<>();
|
||||
java.util.Vector<String> hostFiles = new java.util.Vector<>();
|
||||
boolean[] loaded = new boolean[1];
|
||||
|
||||
xfer.getFiles("PROFILE EXEC A1 V 80 25 1 2026-05-10 14:22:01\n", 0, 0, localFiles, hostFiles, new haus.nightmare.lib3270j.ft.dir.FileTransferHostDirectoryInterface() {
|
||||
@Override
|
||||
public void onDirectoryLoaded(List<? extends HostDirectoryEntry> entries) {
|
||||
loaded[0] = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDirectoryError(String errorMessage) {}
|
||||
});
|
||||
|
||||
assertTrue(loaded[0]);
|
||||
assertEquals(1, hostFiles.size());
|
||||
assertEquals("PROFILE EXEC A1", hostFiles.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMSPrintXferInstantiation() {
|
||||
assertNotNull(xfer.getCMSPrintXfer());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResendInboundDataBufferToHostDelegation() {
|
||||
assertFalse(xfer.resendInboundDataBufferToHost());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package haus.nightmare.lib3270j.ft;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class CMSPrintXferTest {
|
||||
|
||||
@Test
|
||||
public void testAsaCarriageControlTranslation() {
|
||||
// ASA: ' ' = single space, '0' = double space, '-' = triple space, '1' = form feed, '+' = overstrike
|
||||
String input = "1PAGE 1 HEADER\n" +
|
||||
" --------------------\n" +
|
||||
"0ITEM 1 DESCRIPTION\n" +
|
||||
"-ITEM 2 AFTER 3 LINES\n" +
|
||||
"+ITEM 2 UNDERLINED\n" +
|
||||
"1PAGE 2 HEADER\n";
|
||||
|
||||
String expected = "\fPAGE 1 HEADER\n" +
|
||||
"--------------------\n\n" +
|
||||
"ITEM 1 DESCRIPTION\n\n\n" +
|
||||
"ITEM 2 AFTER 3 LINES\r" +
|
||||
"ITEM 2 UNDERLINED\n\f" +
|
||||
"PAGE 2 HEADER";
|
||||
|
||||
String result = CMSPrintXfer.convertAsaCarriageControl(input);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsaCarriageControlBytesConversion() {
|
||||
String input = " REPORT TITLE\n0SUBTITLE\n";
|
||||
byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] converted = CMSPrintXfer.convertAsaCarriageControl(inputBytes, false);
|
||||
String convertedStr = new String(converted, StandardCharsets.UTF_8);
|
||||
|
||||
assertEquals("REPORT TITLE\n\nSUBTITLE", convertedStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMachineCarriageControlTranslation() {
|
||||
// 0x09: Write and advance 1 line
|
||||
// 0x11: Write and advance 2 lines
|
||||
// 0x89: Write and skip to channel 1 (form feed)
|
||||
byte[] input = new byte[] {
|
||||
0x09, 'H', 'E', 'L', 'L', 'O', '\n',
|
||||
0x11, 'W', 'O', 'R', 'L', 'D', '\n',
|
||||
(byte) 0x89, 'N', 'E', 'X', 'T', ' ', 'P', 'A', 'G', 'E', '\n'
|
||||
};
|
||||
|
||||
byte[] output = CMSPrintXfer.convertMachineCarriageControl(input);
|
||||
String outputStr = new String(output, StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(outputStr.startsWith("HELLO\n"));
|
||||
assertTrue(outputStr.contains("WORLD\n\n"));
|
||||
assertTrue(outputStr.contains("NEXT PAGE\n\f"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseQueryReaderOutput() {
|
||||
String sampleRdrOutput =
|
||||
"ORIGINID FILE CLASS RECORDS CPY HOLD DATE TIME NAME TYPE DIST\n" +
|
||||
"MAINT 0042 A RDR 00000120 001 NONE 08/31 14:15:22 PROFILE EXEC SYSTEM\n" +
|
||||
"OPERATOR 0099 B RDR 00000500 002 USER 08/31 14:20:00 PAYROLL DATA FINANCE\n";
|
||||
|
||||
List<CMSPrintXfer.SpoolFileEntry> entries = CMSPrintXfer.parseQueryReaderOutput(sampleRdrOutput);
|
||||
assertEquals(2, entries.size());
|
||||
|
||||
CMSPrintXfer.SpoolFileEntry entry1 = entries.get(0);
|
||||
assertEquals(42, entry1.getSpoolId());
|
||||
assertEquals("MAINT", entry1.getOwner());
|
||||
assertEquals("A", entry1.getSpoolClass());
|
||||
assertEquals(120, entry1.getRecords());
|
||||
assertEquals(1, entry1.getCopies());
|
||||
assertEquals("NONE", entry1.getHoldStatus());
|
||||
assertEquals("08/31", entry1.getDate());
|
||||
assertEquals("14:15:22", entry1.getTime());
|
||||
assertEquals("PROFILE", entry1.getFileName());
|
||||
assertEquals("EXEC", entry1.getFileType());
|
||||
assertEquals("RDR", entry1.getDeviceType());
|
||||
|
||||
CMSPrintXfer.SpoolFileEntry entry2 = entries.get(1);
|
||||
assertEquals(99, entry2.getSpoolId());
|
||||
assertEquals("OPERATOR", entry2.getOwner());
|
||||
assertEquals("PAYROLL", entry2.getFileName());
|
||||
assertEquals("DATA", entry2.getFileType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseQueryPrinterOutput() {
|
||||
String samplePrtOutput =
|
||||
"ORIGINID FILE CLASS RECORDS CPY HOLD DATE TIME NAME TYPE DIST\n" +
|
||||
"USER01 0105 A PRT 00000045 001 USER 08/31 15:00:00 REPORT LISTING OFFICE\n";
|
||||
|
||||
List<CMSPrintXfer.SpoolFileEntry> entries = CMSPrintXfer.parseQueryPrinterOutput(samplePrtOutput);
|
||||
assertEquals(1, entries.size());
|
||||
assertEquals(105, entries.get(0).getSpoolId());
|
||||
assertEquals("USER01", entries.get(0).getOwner());
|
||||
assertEquals("REPORT", entries.get(0).getFileName());
|
||||
assertEquals("LISTING", entries.get(0).getFileType());
|
||||
assertEquals("PRT", entries.get(0).getDeviceType());
|
||||
}
|
||||
}
|
||||
@@ -80,4 +80,49 @@ public class FTConfigTest {
|
||||
|
||||
assertEquals("IND$FILE PUT TEST FILE A (ASCII CRLF RECFM V LRECL 132", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseOptionsComprehensive() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("MY.DATASET");
|
||||
config.parseOptions("TSO ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120) SPACE(10,5) TRACKS REPLACE CODEPAGE(CP037) BUFFERSIZE(8192)");
|
||||
|
||||
assertEquals(FTConfig.HostType.TSO, config.getHostType());
|
||||
assertEquals(FTConfig.TransferMode.ASCII, config.getTransferMode());
|
||||
assertEquals(FTConfig.CrAction.REMOVE, config.getCrAction());
|
||||
assertEquals(FTConfig.RecordFormat.FIXED, config.getRecfm());
|
||||
assertEquals(80, config.getLrecl());
|
||||
assertEquals(3120, config.getBlksize());
|
||||
assertEquals(10, config.getPrimarySpace());
|
||||
assertEquals(5, config.getSecondarySpace());
|
||||
assertEquals(FTConfig.AllocationUnit.TRACKS, config.getUnits());
|
||||
assertEquals(FTConfig.ExistAction.REPLACE, config.getExistAction());
|
||||
assertEquals("CP037", config.getCodePage());
|
||||
assertEquals(8192, config.getDftBufferSize());
|
||||
|
||||
String cmd = config.buildCommand();
|
||||
assertTrue(cmd.contains("RECFM(F)"));
|
||||
assertTrue(cmd.contains("LRECL(80)"));
|
||||
assertTrue(cmd.contains("BLKSIZE(3120)"));
|
||||
assertTrue(cmd.contains("SPACE(10,5)"));
|
||||
assertTrue(cmd.contains("TRACKS"));
|
||||
assertTrue(cmd.contains("REPLACE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCicsCommandGeneration() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setHostType(FTConfig.HostType.CICS);
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("FILE01");
|
||||
config.parseOptions("CICS BINARY NOCRLF REPLACE");
|
||||
|
||||
assertEquals(FTConfig.HostType.CICS, config.getHostType());
|
||||
assertEquals(FTConfig.TransferMode.BINARY, config.getTransferMode());
|
||||
assertEquals(FTConfig.ExistAction.REPLACE, config.getExistAction());
|
||||
|
||||
String cmd = config.buildCommand();
|
||||
assertEquals("IND$FILE PUT FILE01 (BINARY NOCRLF REPLACE", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,4 +500,80 @@ public class FTDftTest {
|
||||
|
||||
assertArrayEquals(firstSent, retransmitted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendDataPacketDirectWithMtuSplitting() {
|
||||
ftDft.setMTUSize(512);
|
||||
assertEquals(512, ftDft.getMTUSize());
|
||||
|
||||
// Send a 1000-byte payload, which should be split into 3 structured field chunks (each max 512 - 27 = 485 bytes)
|
||||
byte[] testPayload = new byte[1000];
|
||||
for (int i = 0; i < testPayload.length; i++) {
|
||||
testPayload[i] = (byte) (i & 0xFF);
|
||||
}
|
||||
|
||||
ftDft.sendDataPacket(testPayload);
|
||||
|
||||
assertEquals(3, inputProcessor.sentStructuredFields.size());
|
||||
assertEquals(1000, ftDft.getBytesTransferred());
|
||||
|
||||
// Verify that each structured field is SF_TRANSFER_DATA (0xD0) and has TR_GET_REPLY (0x4605)
|
||||
for (byte[] sf : inputProcessor.sentStructuredFields) {
|
||||
assertEquals(AID_SF, sf[0] & 0xFF);
|
||||
assertEquals(SF_TRANSFER_DATA, sf[3] & 0xFF);
|
||||
assertEquals(0x46, sf[4] & 0xFF);
|
||||
assertEquals(0x05, sf[5] & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCicsHostMessageHandling() {
|
||||
// DFH0500 File transfer completed successfully
|
||||
String successMsg = "DFH0500 File transfer completed successfully";
|
||||
byte[] msgBytes = successMsg.getBytes(StandardCharsets.ISO_8859_1);
|
||||
int totalPayloadLen = msgBytes.length + 5;
|
||||
|
||||
ByteArrayOutputStream sfOut = new ByteArrayOutputStream();
|
||||
sfOut.write(0); sfOut.write(0);
|
||||
sfOut.write(0xD0);
|
||||
sfOut.write((FTConstants.TR_DATA_INSERT >> 8) & 0xFF);
|
||||
sfOut.write(FTConstants.TR_DATA_INSERT & 0xFF);
|
||||
sfOut.write((FTConstants.TR_NOT_COMPRESSED >> 8) & 0xFF);
|
||||
sfOut.write(FTConstants.TR_NOT_COMPRESSED & 0xFF);
|
||||
sfOut.write(FTConstants.TR_BEGIN_DATA);
|
||||
sfOut.write((totalPayloadLen >> 8) & 0xFF);
|
||||
sfOut.write(totalPayloadLen & 0xFF);
|
||||
sfOut.write(msgBytes, 0, msgBytes.length);
|
||||
|
||||
byte[] insertData = sfOut.toByteArray();
|
||||
int sfLen = insertData.length;
|
||||
insertData[0] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
insertData[1] = (byte) (sfLen & 0xFF);
|
||||
|
||||
// Open message stream first
|
||||
byte[] openReq = new byte[] {
|
||||
0x00, 0x23, (byte) 0xD0, 0x00, 0x12, 0x01, 0x06, 0x01, 0x01, 0x04,
|
||||
0x03, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x11, 0x01, 0x01,
|
||||
0x00, 0x50, 0x05, 0x52, 0x03, (byte) 0xF0, 0x03, 0x09,
|
||||
0x46, 0x54, 0x3A, 0x4D, 0x53, 0x47, 0x20
|
||||
};
|
||||
ftDft.processStructuredField(openReq, 0, openReq.length);
|
||||
|
||||
// Deliver success message
|
||||
ftDft.processStructuredField(insertData, 0, insertData.length);
|
||||
assertTrue(listener.completeCalled);
|
||||
assertFalse(listener.abortCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResendInboundDataBufferToHost() {
|
||||
assertFalse(ftDft.resendInboundDataBufferToHost());
|
||||
|
||||
byte[] payload = "TEST BUFFER".getBytes(StandardCharsets.UTF_8);
|
||||
ftDft.sendDataPacket(payload);
|
||||
|
||||
assertEquals(1, inputProcessor.sentStructuredFields.size());
|
||||
assertTrue(ftDft.resendInboundDataBufferToHost());
|
||||
assertEquals(2, inputProcessor.sentStructuredFields.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ public class InputProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAidAlwaysSendsStandard3270StreamEvenIfGraphicCursorActive() {
|
||||
public void testSendAidWhenGraphicCursorActiveSendsGraphicInputStructuredField() {
|
||||
screen.erase(false);
|
||||
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||
screen.getCell(1).ec = (byte) 0xC1; // 'A'
|
||||
@@ -462,6 +462,55 @@ public class InputProcessorTest {
|
||||
haus.nightmare.lib3270j.graphics.GraphicsPlane plane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600);
|
||||
haus.nightmare.lib3270j.graphics.GocaDecoder goca = new haus.nightmare.lib3270j.graphics.GocaDecoder(plane);
|
||||
goca.setGraphicsCursorActive(true);
|
||||
goca.setGraphicCursorPosition(100, -50);
|
||||
|
||||
java.util.concurrent.atomic.AtomicReference<byte[]> sent = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||
@Override
|
||||
protected void sendAidResponse(byte[] data) {
|
||||
sent.set(data);
|
||||
}
|
||||
};
|
||||
input.setGocaDecoder(goca);
|
||||
|
||||
input.sendAid(AID_ENTER);
|
||||
|
||||
byte[] result = sent.get();
|
||||
assertNotNull(result);
|
||||
// Format: AID_SF (1) + SF (56) + AID_ENTER (1) + Cursor Addr (2) + SBA (1) + Field Addr (2) + Data 'A' (1) = 64 bytes
|
||||
assertEquals(64, result.length);
|
||||
assertEquals((byte) AID_SF, result[0]);
|
||||
assertEquals(0x00, result[1]);
|
||||
assertEquals(0x34, result[2]); // SF len = 52
|
||||
assertEquals(0x0F, result[3]);
|
||||
assertEquals(0x0F, result[4]);
|
||||
// Coordinates in SF at index 1 + 24 = 25
|
||||
int gx = (result[25] << 8) | (result[26] & 0xFF);
|
||||
int gy = (result[27] << 8) | (result[28] & 0xFF);
|
||||
assertEquals(100, (short) gx);
|
||||
assertEquals(-50, (short) gy);
|
||||
// Keyboard AID trigger class constant (0x07) at index 1 + 31 = 32
|
||||
assertEquals(0x07, result[32]);
|
||||
assertEquals(0x07, result[34]);
|
||||
assertEquals((byte) 0xFF, result[35]);
|
||||
assertEquals((byte) AID_ENTER, result[36]);
|
||||
|
||||
// Trailing AID at index 57
|
||||
assertEquals((byte) AID_ENTER, result[57]);
|
||||
assertEquals((byte) ORDER_SBA, result[60]);
|
||||
assertEquals((byte) 0xC1, result[63]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAidWhenGraphicCursorNotActiveSendsStandard3270Stream() {
|
||||
screen.erase(false);
|
||||
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||
screen.getCell(1).ec = (byte) 0xC1; // 'A'
|
||||
screen.setCursorAddress(2);
|
||||
|
||||
haus.nightmare.lib3270j.graphics.GraphicsPlane plane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600);
|
||||
haus.nightmare.lib3270j.graphics.GocaDecoder goca = new haus.nightmare.lib3270j.graphics.GocaDecoder(plane);
|
||||
goca.setGraphicsCursorActive(false);
|
||||
|
||||
java.util.concurrent.atomic.AtomicReference<byte[]> sent = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
package haus.nightmare.lib3270j.nvt;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Comprehensive Unit Tests for Phase 9: NVT Mode (VT100 / VT220 / VT320 / ANSI Emulation).
|
||||
*/
|
||||
public class NvtProcessorPhase9Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private NvtProcessor processor;
|
||||
private ByteArrayOutputStream output;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator); // 24x80
|
||||
processor = new NvtProcessor(screen, translator);
|
||||
output = new ByteArrayOutputStream();
|
||||
processor.setOutputSender(bytes -> output.write(bytes, 0, bytes.length));
|
||||
}
|
||||
|
||||
private void feed(String s) {
|
||||
byte[] b = s.getBytes(StandardCharsets.US_ASCII);
|
||||
processor.processNVTData(b, 0, b.length);
|
||||
}
|
||||
|
||||
private void feedUtf8(String s) {
|
||||
byte[] b = s.getBytes(StandardCharsets.UTF_8);
|
||||
processor.processNVTData(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeviceStatusReportExtendedDSR() {
|
||||
output.reset();
|
||||
// DSR 5 (Status OK)
|
||||
feed("\u001B[5n");
|
||||
assertEquals("\u001B[0n", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DSR 6 (Cursor Position Report) -> at row 10, col 20 (0-indexed: 9, 19)
|
||||
feed("\u001B[10;20H");
|
||||
feed("\u001B[6n");
|
||||
assertEquals("\u001B[10;20R", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DSR ?6 (DECXCPR Extended Cursor Position Report)
|
||||
feed("\u001B[?6n");
|
||||
assertEquals("\u001B[?10;20;1R", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DSR ?15 (Printer status)
|
||||
feed("\u001B[?15n");
|
||||
assertEquals("\u001B[?13n", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DSR ?26 (Keyboard dialect status)
|
||||
feed("\u001B[?26n");
|
||||
assertEquals("\u001B[?27;1n", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DSR ?62 / ?63 (Macro/Memory Checksum)
|
||||
feed("\u001B[?62n");
|
||||
assertEquals("\u001B[?63;0n", output.toString(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeviceAttributesPrimaryAndSecondaryDA() {
|
||||
output.reset();
|
||||
// Default Primary DA (VT100 with AVO): ESC [ c
|
||||
feed("\u001B[c");
|
||||
assertEquals("\u001B[?1;2c", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// Configure VT220 Primary DA
|
||||
processor.setPrimaryDeviceAttributes("\u001B[?62;1;2;6;7;8;9c");
|
||||
feed("\u001B[c");
|
||||
assertEquals("\u001B[?62;1;2;6;7;8;9c", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
feed("\u001B[0c");
|
||||
assertEquals("\u001B[?62;1;2;6;7;8;9c", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// Secondary DA: ESC [ > c
|
||||
feed("\u001B[>c");
|
||||
assertEquals("\u001B[>1;10;0c", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// Secondary DA: ESC [ > 0 c
|
||||
feed("\u001B[>0c");
|
||||
assertEquals("\u001B[>1;10;0c", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DECREQTPARM: ESC [ 1 x
|
||||
feed("\u001B[1x");
|
||||
assertEquals("\u001B[3;1;1;120;120;1;0x", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
output.reset();
|
||||
// DECREQTPARM: ESC [ 0 x
|
||||
feed("\u001B[0x");
|
||||
assertEquals("\u001B[2;1;1;120;120;1;0x", output.toString(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecSoftTerminalResetDECSTR() {
|
||||
// Set non-standard modes
|
||||
processor.setCursorVisible(false);
|
||||
processor.setApplicationKeypad(true);
|
||||
processor.setApplicationCursorKeys(true);
|
||||
processor.setOriginMode(true);
|
||||
processor.setAutoWrap(false);
|
||||
processor.setInsertMode(true);
|
||||
|
||||
// Set scroll region 3..10
|
||||
feed("\u001B[3;10r");
|
||||
|
||||
// Soft Reset: ESC [ ! p
|
||||
feed("\u001B[!p");
|
||||
|
||||
assertTrue(processor.isCursorVisible());
|
||||
assertFalse(processor.isApplicationKeypad());
|
||||
assertFalse(processor.isApplicationCursorKeys());
|
||||
assertFalse(processor.isOriginMode());
|
||||
assertTrue(processor.isAutoWrap());
|
||||
assertFalse(processor.isInsertMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOriginModeDECOM() {
|
||||
// Set scroll region lines 5..15: ESC [ 5 ; 15 r
|
||||
feed("\u001B[5;15r");
|
||||
|
||||
// Enable Origin Mode: ESC [ ? 6 h
|
||||
feed("\u001B[?6h");
|
||||
assertTrue(processor.isOriginMode());
|
||||
|
||||
// Move to line 1, col 1 in origin mode -> corresponds to row 5 (0-indexed: 4), col 0
|
||||
feed("\u001B[1;1H");
|
||||
assertEquals(4 * 80, screen.getCursorAddress());
|
||||
|
||||
// Move to line 3, col 10 in origin mode -> corresponds to row 7 (0-indexed: 6), col 9
|
||||
feed("\u001B[3;10H");
|
||||
assertEquals(6 * 80 + 9, screen.getCursorAddress());
|
||||
|
||||
// Check DSR extended CPR in origin mode: should report row 3, col 10 relative to top margin
|
||||
output.reset();
|
||||
feed("\u001B[?6n");
|
||||
assertEquals("\u001B[?3;10;1R", output.toString(StandardCharsets.US_ASCII));
|
||||
|
||||
// Disable Origin Mode: ESC [ ? 6 l
|
||||
feed("\u001B[?6l");
|
||||
assertFalse(processor.isOriginMode());
|
||||
|
||||
// Move to line 1, col 1 in absolute mode -> row 0, col 0
|
||||
feed("\u001B[1;1H");
|
||||
assertEquals(0, screen.getCursorAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharacterSetSCSandShifts() {
|
||||
// Designate G0 as UK (ESC ( A), G1 as Line Drawing (ESC ) 0), G2 as German (ESC * K)
|
||||
feed("\u001B(A\u001B)0\u001B*K");
|
||||
assertEquals('A', processor.getG0Charset());
|
||||
assertEquals('0', processor.getG1Charset());
|
||||
assertEquals('K', processor.getG2Charset());
|
||||
|
||||
// In G0 (UK): '#' maps to '£'
|
||||
feed("\u001B[1;1H#");
|
||||
assertEquals('£', screen.getCell(0).ucs4);
|
||||
|
||||
// Shift-Out (0x0E -> G1 Line Drawing)
|
||||
processor.processNVTData(new byte[] { 0x0E, (byte) 'q' }, 0, 2);
|
||||
assertEquals('─', screen.getCell(1).ucs4);
|
||||
|
||||
// Shift-In (0x0F -> G0 UK)
|
||||
processor.processNVTData(new byte[] { 0x0F, (byte) '#' }, 0, 2);
|
||||
assertEquals('£', screen.getCell(2).ucs4);
|
||||
|
||||
// Single Shift SS2 (ESC N -> G2 German for next character only)
|
||||
// In German: '@' -> '§', '{' -> 'ä'
|
||||
feed("\u001BN@#");
|
||||
assertEquals('§', screen.getCell(3).ucs4); // Single shift applied to '@'
|
||||
assertEquals('£', screen.getCell(4).ucs4); // Returned to G0 UK for '#'
|
||||
|
||||
// Locking Shift LS2 (ESC n -> G2 German active)
|
||||
feed("\u001Bn{|}~");
|
||||
assertEquals('ä', screen.getCell(5).ucs4);
|
||||
assertEquals('ö', screen.getCell(6).ucs4);
|
||||
assertEquals('ü', screen.getCell(7).ucs4);
|
||||
assertEquals('ß', screen.getCell(8).ucs4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOscWindowTitleAndIcon() {
|
||||
AtomicReference<String> titleRef = new AtomicReference<>("");
|
||||
processor.addTitleListener(titleRef::set);
|
||||
|
||||
// OSC 0 (BEL terminated): ESC ] 0 ; Main Terminal Window BEL
|
||||
feedUtf8("\u001B]0;Main Terminal Window\u0007");
|
||||
|
||||
assertEquals("Main Terminal Window", processor.getTerminalTitle());
|
||||
assertEquals("Main Terminal Window", titleRef.get());
|
||||
|
||||
// OSC 2 (ST terminated): ESC ] 2 ; IBM Host Session ESC \
|
||||
feedUtf8("\u001B]2;IBM Host Session\u001B\\");
|
||||
|
||||
assertEquals("IBM Host Session", processor.getTerminalTitle());
|
||||
assertEquals("IBM Host Session", titleRef.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsc52ClipboardReadAndWrite() {
|
||||
AtomicReference<String> clipboardStorage = new AtomicReference<>("Initial Clipboard");
|
||||
processor.setClipboardHandler(new NvtProcessor.ClipboardHandler() {
|
||||
@Override
|
||||
public String getClipboardText() {
|
||||
return clipboardStorage.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClipboardText(String text) {
|
||||
clipboardStorage.set(text);
|
||||
}
|
||||
});
|
||||
|
||||
// OSC 52 Write: Base64 of "Hello from Host" is "SGVsbG8gZnJvbSBIb3N0"
|
||||
feed("\u001B]52;c;SGVsbG8gZnJvbSBIb3N0\u0007");
|
||||
|
||||
assertEquals("Hello from Host", clipboardStorage.get());
|
||||
|
||||
// OSC 52 Read Query: ESC ] 52 ; c ; ? BEL
|
||||
output.reset();
|
||||
feed("\u001B]52;c;?\u0007");
|
||||
|
||||
String expectedB64 = java.util.Base64.getEncoder().encodeToString("Hello from Host".getBytes(StandardCharsets.UTF_8));
|
||||
String response = output.toString(StandardCharsets.US_ASCII);
|
||||
assertTrue(response.contains(expectedB64));
|
||||
assertTrue(response.startsWith("\u001B]52;c;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplicationKeypadModeDECKPAM() {
|
||||
assertFalse(processor.isApplicationKeypad());
|
||||
|
||||
// Enable Keypad Application Mode: ESC =
|
||||
feed("\u001B=");
|
||||
assertTrue(processor.isApplicationKeypad());
|
||||
|
||||
// Test keypad mapping
|
||||
assertEquals("\u001BOp", processor.mapKeypadKey(0x60)); // NUMPAD0
|
||||
assertEquals("\u001BOq", processor.mapKeypadKey(0x61)); // NUMPAD1
|
||||
assertEquals("\u001BOr", processor.mapKeypadKey(0x62)); // NUMPAD2
|
||||
assertEquals("\u001BOs", processor.mapKeypadKey(0x63)); // NUMPAD3
|
||||
assertEquals("\u001BOt", processor.mapKeypadKey(0x64)); // NUMPAD4
|
||||
assertEquals("\u001BOu", processor.mapKeypadKey(0x65)); // NUMPAD5
|
||||
assertEquals("\u001BOv", processor.mapKeypadKey(0x66)); // NUMPAD6
|
||||
assertEquals("\u001BOw", processor.mapKeypadKey(0x67)); // NUMPAD7
|
||||
assertEquals("\u001BOx", processor.mapKeypadKey(0x68)); // NUMPAD8
|
||||
assertEquals("\u001BOy", processor.mapKeypadKey(0x69)); // NUMPAD9
|
||||
assertEquals("\u001BOm", processor.mapKeypadKey(0x6D)); // MINUS
|
||||
assertEquals("\u001BOn", processor.mapKeypadKey(0x6E)); // DECIMAL
|
||||
assertEquals("\u001BOM", processor.mapKeypadKey(0x0A)); // ENTER
|
||||
|
||||
byte[] bytes = processor.processApplicationKeypad(0x60);
|
||||
assertNotNull(bytes);
|
||||
assertEquals("\u001BOp", new String(bytes, StandardCharsets.US_ASCII));
|
||||
|
||||
// Enable Keypad Numeric Mode: ESC >
|
||||
feed("\u001B>");
|
||||
assertFalse(processor.isApplicationKeypad());
|
||||
assertNull(processor.mapKeypadKey(0x60));
|
||||
assertNull(processor.processApplicationKeypad(0x60));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplicationCursorKeysModeDECCKM() {
|
||||
assertFalse(processor.isApplicationCursorKeys());
|
||||
assertEquals("\u001B[A", processor.mapKey(0x26, '\0', false, false, false)); // VK_UP normal
|
||||
assertEquals("\u001B[B", processor.mapKey(0x28, '\0', false, false, false)); // VK_DOWN normal
|
||||
assertEquals("\u001B[C", processor.mapKey(0x27, '\0', false, false, false)); // VK_RIGHT normal
|
||||
assertEquals("\u001B[D", processor.mapKey(0x25, '\0', false, false, false)); // VK_LEFT normal
|
||||
|
||||
// Enable Application Cursor Keys: ESC [ ? 1 h
|
||||
feed("\u001B[?1h");
|
||||
assertTrue(processor.isApplicationCursorKeys());
|
||||
|
||||
assertEquals("\u001BOA", processor.mapKey(0x26, '\0', false, false, false)); // VK_UP application
|
||||
assertEquals("\u001BOB", processor.mapKey(0x28, '\0', false, false, false)); // VK_DOWN application
|
||||
assertEquals("\u001BOC", processor.mapKey(0x27, '\0', false, false, false)); // VK_RIGHT application
|
||||
assertEquals("\u001BOD", processor.mapKey(0x25, '\0', false, false, false)); // VK_LEFT application
|
||||
|
||||
// Disable Application Cursor Keys: ESC [ ? 1 l
|
||||
feed("\u001B[?1l");
|
||||
assertFalse(processor.isApplicationCursorKeys());
|
||||
assertEquals("\u001B[A", processor.mapKey(0x26, '\0', false, false, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionAndEditingKeyMapping() {
|
||||
// Function keys F1..F4
|
||||
assertEquals("\u001BOP", processor.mapKey(0x70, '\0', false, false, false)); // F1
|
||||
assertEquals("\u001BOQ", processor.mapKey(0x71, '\0', false, false, false)); // F2
|
||||
assertEquals("\u001BOR", processor.mapKey(0x72, '\0', false, false, false)); // F3
|
||||
assertEquals("\u001BOS", processor.mapKey(0x73, '\0', false, false, false)); // F4
|
||||
|
||||
// Function keys F5..F12
|
||||
assertEquals("\u001B[15~", processor.mapKey(0x74, '\0', false, false, false)); // F5
|
||||
assertEquals("\u001B[17~", processor.mapKey(0x75, '\0', false, false, false)); // F6
|
||||
assertEquals("\u001B[24~", processor.mapKey(0x7B, '\0', false, false, false)); // F12
|
||||
|
||||
// Editing keys
|
||||
assertEquals("\u001B[2~", processor.mapKey(0x9B, '\0', false, false, false)); // Insert
|
||||
assertEquals("\u001B[3~", processor.mapKey(0x7F, '\0', false, false, false)); // Delete
|
||||
assertEquals("\u001B[1~", processor.mapKey(0x24, '\0', false, false, false)); // Home
|
||||
assertEquals("\u001B[4~", processor.mapKey(0x23, '\0', false, false, false)); // End
|
||||
assertEquals("\u001B[5~", processor.mapKey(0x21, '\0', false, false, false)); // PageUp
|
||||
assertEquals("\u001B[6~", processor.mapKey(0x22, '\0', false, false, false)); // PageDown
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertModeAndNewLineMode() throws IOException {
|
||||
// Line Feed in standard mode: cursor moves down without returning to col 0
|
||||
processor.setNewLineMode(false);
|
||||
feed("\u001B[1;10H\n");
|
||||
assertEquals(1 * 80 + 9, screen.getCursorAddress()); // Row 2, Col 10
|
||||
|
||||
// Enable New Line Mode: ESC [ 20 h
|
||||
feed("\u001B[20h");
|
||||
assertTrue(processor.isNewLineMode());
|
||||
|
||||
feed("\u001B[1;10H\n");
|
||||
assertEquals(1 * 80, screen.getCursorAddress()); // Row 2, Col 0 (CR + LF)
|
||||
|
||||
// Insert Mode: ESC [ 4 h
|
||||
feed("\u001B[1;1HAC");
|
||||
feed("\u001B[1;2H"); // Move cursor between A and C
|
||||
feed("\u001B[4h"); // Enable Insert Mode
|
||||
assertTrue(processor.isInsertMode());
|
||||
|
||||
feed("B");
|
||||
assertEquals('A', screen.getCell(0).ucs4);
|
||||
assertEquals('B', screen.getCell(1).ucs4);
|
||||
assertEquals('C', screen.getCell(2).ucs4);
|
||||
|
||||
// Reset Insert Mode: ESC [ 4 l
|
||||
feed("\u001B[4l");
|
||||
assertFalse(processor.isInsertMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoWrapAndColumnMode() {
|
||||
// Disable Auto Wrap: ESC [ ? 7 l
|
||||
feed("\u001B[?7l");
|
||||
assertFalse(processor.isAutoWrap());
|
||||
|
||||
// Move to col 79 (0-indexed: 78) on line 1, and write 5 characters
|
||||
feed("\u001B[1;79H12345");
|
||||
// Characters should truncate / overwrite at margin (col 79) and not wrap to next row
|
||||
assertEquals('5', screen.getCell(79).ucs4);
|
||||
assertEquals(79, screen.getCursorAddress());
|
||||
|
||||
// Enable Auto Wrap: ESC [ ? 7 h
|
||||
feed("\u001B[?7h");
|
||||
assertTrue(processor.isAutoWrap());
|
||||
|
||||
// 132-Column Mode switch: ESC [ ? 3 h (clears screen)
|
||||
feed("\u001B[?3h");
|
||||
assertEquals(0, screen.getCursorAddress());
|
||||
assertEquals(0, screen.getCell(0).ucs4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package haus.nightmare.lib3270j.printer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for Phase 7 Printer Definition Table (PDT) profiles, control sequence generation,
|
||||
* character mappings, and GDDM host escapes in PD3270.
|
||||
*/
|
||||
public class PD3270PDTPhase7Test {
|
||||
|
||||
private PrinterConfig config;
|
||||
private PD3270 pd;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new PrinterConfig("localhost", 23);
|
||||
pd = new PD3270(config);
|
||||
pd.openPrinter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuiltinPDTPresets() {
|
||||
// Plain Text
|
||||
PrinterDefinitionTable textPdt = PrinterDefinitionTable.createPlainTextPDT();
|
||||
assertEquals("PLAIN_TEXT", textPdt.getName());
|
||||
assertTrue(textPdt.hasControlCode(PrinterDefinitionTable.CMD_PAGE_FEED));
|
||||
|
||||
// HP PCL 5
|
||||
PrinterDefinitionTable pcl5 = PrinterDefinitionTable.createPcl5PDT();
|
||||
assertEquals("PCL_5", pcl5.getName());
|
||||
assertTrue(pcl5.hasControlCode(PrinterDefinitionTable.CMD_START_BOLD));
|
||||
assertArrayEquals("\033(s3B".getBytes(), pcl5.getControlCode(PrinterDefinitionTable.CMD_START_BOLD));
|
||||
|
||||
// Epson ESC/P
|
||||
PrinterDefinitionTable escp = PrinterDefinitionTable.createEpsonEscPPDT();
|
||||
assertEquals("EPSON_ESC_P", escp.getName());
|
||||
assertTrue(escp.hasControlCode(PrinterDefinitionTable.CMD_START_BOLD));
|
||||
assertArrayEquals("\033E".getBytes(), escp.getControlCode(PrinterDefinitionTable.CMD_START_BOLD));
|
||||
|
||||
// PostScript
|
||||
PrinterDefinitionTable ps = PrinterDefinitionTable.createPostScriptPDT();
|
||||
assertEquals("POSTSCRIPT", ps.getName());
|
||||
assertTrue(ps.hasControlCode(PrinterDefinitionTable.CMD_START_JOB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPcl5ControlCodeGeneration() {
|
||||
pd.setPDT(PrinterDefinitionTable.createPcl5PDT());
|
||||
|
||||
pd.startJob();
|
||||
pd.setBold(true);
|
||||
pd.print("BOLD TEXT");
|
||||
pd.setBold(false);
|
||||
pd.setUnderline(true);
|
||||
pd.print("UNDERLINED");
|
||||
pd.setUnderline(false);
|
||||
pd.setItalic(true);
|
||||
pd.print("ITALIC");
|
||||
pd.setItalic(false);
|
||||
pd.setCPI(12);
|
||||
pd.setLPI(8);
|
||||
pd.formFeed();
|
||||
pd.endJob();
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("\033E")); // Reset / start job
|
||||
assertTrue(captured.contains("\033(s3B")); // Bold on
|
||||
assertTrue(captured.contains("\033(s0B")); // Bold off
|
||||
assertTrue(captured.contains("\033&d0D")); // Underline on
|
||||
assertTrue(captured.contains("\033(s1S")); // Italic on
|
||||
assertTrue(captured.contains("\033&k2S")); // 12 CPI
|
||||
assertTrue(captured.contains("\033&l8D")); // 8 LPI
|
||||
assertTrue(captured.contains("\f")); // Page feed
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEpsonEscPControlCodeGeneration() {
|
||||
pd.setPDT(PrinterDefinitionTable.createEpsonEscPPDT());
|
||||
|
||||
pd.startJob();
|
||||
pd.setDoubleStrike(true);
|
||||
pd.print("DOUBLE STRIKE");
|
||||
pd.setDoubleStrike(false);
|
||||
pd.setDoubleWidth(true);
|
||||
pd.print("DOUBLE WIDTH");
|
||||
pd.setDoubleWidth(false);
|
||||
pd.setSuperscript(true);
|
||||
pd.print("SUPER");
|
||||
pd.setSuperscript(false);
|
||||
pd.setSubscript(true);
|
||||
pd.print("SUB");
|
||||
pd.setSubscript(false);
|
||||
pd.setCPI(10);
|
||||
pd.setLPI(6);
|
||||
pd.endJob();
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("\033@")); // Init
|
||||
assertTrue(captured.contains("\033G")); // Double strike on
|
||||
assertTrue(captured.contains("\033H")); // Double strike off
|
||||
assertTrue(captured.contains("\033W1")); // Double width on
|
||||
assertTrue(captured.contains("\033W0")); // Double width off
|
||||
assertTrue(captured.contains("\033S0")); // Superscript on
|
||||
assertTrue(captured.contains("\033S1")); // Subscript on
|
||||
assertTrue(captured.contains("\033P")); // 10 CPI
|
||||
assertTrue(captured.contains("\0332")); // 6 LPI
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPDTWithCharacterOverrides() {
|
||||
PrinterDefinitionTable customPdt = new PrinterDefinitionTable("CUSTOM", "Custom Test Table");
|
||||
customPdt.setControlCode("TEST_CMD", "[CMD]");
|
||||
customPdt.setCharOverride('@', "[AT]".getBytes());
|
||||
|
||||
pd.setPDT(customPdt);
|
||||
pd.writeControlCode("TEST_CMD");
|
||||
pd.print('@');
|
||||
pd.print("user@ibm.com");
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("[CMD]"));
|
||||
assertTrue(captured.contains("[AT]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGddmHostPrintEscapePassthrough() {
|
||||
byte[] gddmEscape = new byte[]{0x1B, '&', 'a', '1', '0', '0', 'V'};
|
||||
pd.processGddmEscape(gddmEscape, 0, gddmEscape.length);
|
||||
|
||||
byte[] captured = pd.getCapturedBytes();
|
||||
assertTrue(captured.length >= gddmEscape.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* Tests for Phase 7 PrintPS3270 EOJ completion, auto-flush timer, and 3270 printer data stream formatting.
|
||||
*/
|
||||
public class PrintPS3270Phase7Test {
|
||||
|
||||
private PrinterConfig config;
|
||||
private PD3270 pd;
|
||||
private EbcdicTranslator translator;
|
||||
private PrintPS3270 printPs;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new PrinterConfig("localhost", 23);
|
||||
config.setAutoFlushTimeoutMs(150); // Fast timeout for testing
|
||||
pd = new PD3270(config);
|
||||
translator = new EbcdicTranslator("037");
|
||||
printPs = new PrintPS3270(config, pd, translator);
|
||||
pd.openPrinter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessPrintCompleteEOJ() {
|
||||
config.setFormFeedAtEoj(true);
|
||||
config.setAutoFlushOnEoj(true);
|
||||
|
||||
byte[] ebcText = translator.stringToEbcdic("LU3 TEST PAGE");
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(DS3270Constants.CMD_WRITE);
|
||||
stream.write(0x00); // WCC (Start Print = false)
|
||||
stream.write(PrinterConstants.ORDER_SBA);
|
||||
stream.write(0x40); // Row 0 Col 0
|
||||
stream.write(0x40);
|
||||
stream.write(ebcText, 0, ebcText.length);
|
||||
|
||||
printPs.process3270PrintDS(stream.toByteArray(), 0, stream.size());
|
||||
|
||||
// Process EOJ completion
|
||||
printPs.processPrintComplete();
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("LU3 TEST PAGE"));
|
||||
assertEquals(1, pd.getPageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoFlushTimerTriggersOnIdle() throws InterruptedException {
|
||||
byte[] ebcText = translator.stringToEbcdic("AUTO FLUSH DATA");
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(DS3270Constants.CMD_WRITE);
|
||||
stream.write(0x00); // WCC with Start Print = 0
|
||||
stream.write(PrinterConstants.ORDER_SBA);
|
||||
stream.write(0x40);
|
||||
stream.write(0x40);
|
||||
stream.write(ebcText, 0, ebcText.length);
|
||||
|
||||
printPs.process3270PrintDS(stream.toByteArray(), 0, stream.size());
|
||||
|
||||
// Wait for auto-flush timer (150ms) to trigger
|
||||
Thread.sleep(300);
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("AUTO FLUSH DATA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartPrintBitImmediateFlush() {
|
||||
byte[] ebcText = translator.stringToEbcdic("IMMEDIATE PRINT");
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(DS3270Constants.CMD_WRITE);
|
||||
stream.write(PrinterConstants.WCC_START_PRINT_BIT); // WCC with Start Print = 1
|
||||
stream.write(PrinterConstants.ORDER_SBA);
|
||||
stream.write(0x40);
|
||||
stream.write(0x40);
|
||||
stream.write(ebcText, 0, ebcText.length);
|
||||
|
||||
printPs.process3270PrintDS(stream.toByteArray(), 0, stream.size());
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertTrue(captured.contains("IMMEDIATE PRINT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelAutoFlush() {
|
||||
printPs.scheduleAutoFlush();
|
||||
printPs.cancelAutoFlush();
|
||||
// Cancel should succeed without exception
|
||||
assertNotNull(printPs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package haus.nightmare.lib3270j.printer;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Comprehensive tests for Phase 7 SCS (SNA Character String) 28-command protocol engine,
|
||||
* Enhanced Highlights, Vertical Channel Select (VCS), Graphic Error Action (GEA),
|
||||
* Index Return (IRS), and Transparent data (TRN).
|
||||
*/
|
||||
public class PrintSCS3270Phase7Test {
|
||||
|
||||
private PrinterConfig config;
|
||||
private PD3270 pd;
|
||||
private EbcdicTranslator translator;
|
||||
private PrintSCS3270 scs;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new PrinterConfig("localhost", 23);
|
||||
pd = new PD3270(config);
|
||||
translator = new EbcdicTranslator("037");
|
||||
scs = new PrintSCS3270(config, pd, translator);
|
||||
pd.openPrinter(null); // In-memory capture
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAll28SCSCommandsDispatch() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
// 1. NUL (0x00) & NOP (0x03)
|
||||
stream.write(PrinterConstants.SCS_NUL);
|
||||
stream.write(PrinterConstants.SCS_NOP);
|
||||
|
||||
// 2. Text "A" (0xC1 in Cp037)
|
||||
stream.write(0xC1);
|
||||
|
||||
// 3. BS (0x16) & NBS (0x36)
|
||||
stream.write(PrinterConstants.SCS_BS);
|
||||
stream.write(PrinterConstants.SCS_NBS);
|
||||
|
||||
// 4. SP (0x40) & RSP (0x41)
|
||||
stream.write(PrinterConstants.SCS_SP);
|
||||
stream.write(PrinterConstants.SCS_RSP);
|
||||
|
||||
// 5. HT (0x05)
|
||||
stream.write(PrinterConstants.SCS_HT);
|
||||
|
||||
// 6. Text "B" (0xC2)
|
||||
stream.write(0xC2);
|
||||
|
||||
// 7. CR (0x0D) & RCR (0x07)
|
||||
stream.write(PrinterConstants.SCS_CR);
|
||||
stream.write(PrinterConstants.SCS_RCR);
|
||||
|
||||
// 8. LF (0x25) & NL (0x15) & RNL (0x06)
|
||||
stream.write(PrinterConstants.SCS_LF);
|
||||
stream.write(PrinterConstants.SCS_NL);
|
||||
stream.write(PrinterConstants.SCS_RNLS);
|
||||
|
||||
// 9. VT (0x0B)
|
||||
stream.write(PrinterConstants.SCS_VT);
|
||||
|
||||
// 10. SO (0x0E) & SI (0x0F)
|
||||
stream.write(PrinterConstants.SCS_SO);
|
||||
stream.write(PrinterConstants.SCS_SI);
|
||||
|
||||
// 11. INP (0x24) & ENP (0x14)
|
||||
stream.write(PrinterConstants.SCS_INP);
|
||||
stream.write(0xC3); // Should be ignored when presentation inhibited
|
||||
stream.write(PrinterConstants.SCS_ENP);
|
||||
stream.write(0xC3); // 'C'
|
||||
|
||||
// 12. BEL (0x2F) & POC (0x17)
|
||||
stream.write(PrinterConstants.SCS_BEL);
|
||||
stream.write(PrinterConstants.SCS_POC);
|
||||
|
||||
// 13. IRS (0x33)
|
||||
stream.write(PrinterConstants.SCS_IRS);
|
||||
|
||||
// 14. SUB (0x3F)
|
||||
stream.write(PrinterConstants.SCS_SUB);
|
||||
|
||||
// 15. FF (0x0C)
|
||||
stream.write(PrinterConstants.SCS_FF);
|
||||
|
||||
byte[] payload = stream.toByteArray();
|
||||
scs.processRecord(payload);
|
||||
|
||||
String captured = pd.getCapturedText();
|
||||
assertNotNull(captured);
|
||||
assertTrue(captured.contains("C"));
|
||||
assertTrue(captured.contains("?"));
|
||||
assertTrue(pd.getPageCount() >= 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessTRNRawBytePassthrough() {
|
||||
// TRN order: 0x35 <len> <rawBytes>
|
||||
byte[] rawPrinterCodes = new byte[]{0x1B, 0x45, 'R', 'A', 'W', 0x1B, 0x46};
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(PrinterConstants.SCS_TRN);
|
||||
stream.write(rawPrinterCodes.length);
|
||||
stream.write(rawPrinterCodes, 0, rawPrinterCodes.length);
|
||||
|
||||
scs.processHostData(stream.toByteArray(), 0, stream.size());
|
||||
|
||||
byte[] captured = pd.getCapturedBytes();
|
||||
assertTrue(captured.length >= rawPrinterCodes.length);
|
||||
|
||||
// Also test direct processTRN method
|
||||
byte[] directBytes = new byte[]{0x1B, 0x2A, 0x62, 0x30, 0x57};
|
||||
scs.processTRN(directBytes);
|
||||
assertTrue(pd.getByteCount() >= rawPrinterCodes.length + directBytes.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessVCSWithChannelTape() {
|
||||
// Setup channel tape: Channel 2 = line 15, Channel 3 = line 30
|
||||
scs.setChannelLine(2, 15);
|
||||
scs.setChannelLine(3, 30);
|
||||
assertEquals(15, scs.getChannelLine(2));
|
||||
assertEquals(30, scs.getChannelLine(3));
|
||||
|
||||
// Start at line 1
|
||||
assertEquals(1, scs.getCurrentRow());
|
||||
|
||||
// Process VCS for Channel 2
|
||||
scs.processVCS(2);
|
||||
assertEquals(15, scs.getCurrentRow());
|
||||
|
||||
// Process VCS for Channel 3
|
||||
scs.processVCS(3);
|
||||
assertEquals(30, scs.getCurrentRow());
|
||||
|
||||
// Process VCS for Channel 1 (line 1, which is < 30, so forms feed to next page)
|
||||
int initialPages = pd.getPageCount();
|
||||
scs.processVCS(1);
|
||||
assertEquals(1, scs.getCurrentRow());
|
||||
assertEquals(initialPages + 1, pd.getPageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessGEAGraphicErrorActions() {
|
||||
// 1. GEA Default: replacement '?'
|
||||
config.setGraphicErrorAction(PrinterConstants.GEA_NO_SUBSTITUTION);
|
||||
scs.processSUB();
|
||||
scs.flushLineBuffer();
|
||||
assertTrue(pd.getCapturedText().contains("?"));
|
||||
|
||||
// 2. GEA Substitute specified character ('#')
|
||||
pd.resetCapture();
|
||||
scs.processGEA(PrinterConstants.GEA_SUBSTITUTE_SPECIFIED, '#');
|
||||
scs.processSUB();
|
||||
scs.flushLineBuffer();
|
||||
assertTrue(pd.getCapturedText().contains("#"));
|
||||
|
||||
// 3. GEA 0x2B 0xC8 order
|
||||
pd.resetCapture();
|
||||
byte[] geaOrder = new byte[]{
|
||||
(byte) PrinterConstants.SCS_PREFIX_2B,
|
||||
(byte) PrinterConstants.SCS_GEA,
|
||||
0x02,
|
||||
PrinterConstants.GEA_SUBSTITUTE_SPECIFIED,
|
||||
(byte) '*'
|
||||
};
|
||||
scs.processHostData(geaOrder, 0, geaOrder.length);
|
||||
scs.processSUB();
|
||||
scs.flushLineBuffer();
|
||||
assertTrue(pd.getCapturedText().contains("*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnhancedHighlightAttributes() {
|
||||
// Set PDT to PCL5 so we can verify escape sequences generated
|
||||
PrinterDefinitionTable pcl5 = PrinterDefinitionTable.createPcl5PDT();
|
||||
pd.setPDT(pcl5);
|
||||
|
||||
// Test Emphasized (Bold)
|
||||
scs.setEnhancedHighlight(PrinterConstants.SEAC_EMPHASIZED);
|
||||
assertEquals(PrinterConstants.SEAC_EMPHASIZED, scs.getActiveHighlight());
|
||||
String text = pd.getCapturedText();
|
||||
assertTrue(text.contains("\033(s3B")); // PCL Bold on
|
||||
|
||||
// Test Italic
|
||||
pd.resetCapture();
|
||||
scs.setEnhancedHighlight(PrinterConstants.SEAC_ITALIC);
|
||||
assertEquals(PrinterConstants.SEAC_ITALIC, scs.getActiveHighlight());
|
||||
text = pd.getCapturedText();
|
||||
assertTrue(text.contains("\033(s1S")); // PCL Italic on
|
||||
|
||||
// Test Underline
|
||||
pd.resetCapture();
|
||||
scs.setEnhancedHighlight(PrinterConstants.SEAC_UNDERLINE);
|
||||
assertEquals(PrinterConstants.SEAC_UNDERLINE, scs.getActiveHighlight());
|
||||
text = pd.getCapturedText();
|
||||
assertTrue(text.contains("\033&d0D")); // PCL Underline on
|
||||
|
||||
// Test Double-Width
|
||||
pd.resetCapture();
|
||||
scs.startDoubleWidthCharacters();
|
||||
assertTrue(scs.isDoubleWidth());
|
||||
text = pd.getCapturedText();
|
||||
assertTrue(text.contains("\033&k1W"));
|
||||
|
||||
scs.endDoubleWidthCharacters();
|
||||
assertFalse(scs.isDoubleWidth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitSCSNamedMethods() {
|
||||
scs.processNL();
|
||||
scs.processCR();
|
||||
scs.processLF();
|
||||
scs.processFF();
|
||||
scs.processBS();
|
||||
scs.processNBS();
|
||||
scs.processHT();
|
||||
scs.processVT();
|
||||
scs.processRNL();
|
||||
scs.processRCR();
|
||||
scs.processENP();
|
||||
scs.processINP();
|
||||
scs.processBEL();
|
||||
scs.processIRS();
|
||||
scs.processSO();
|
||||
scs.processSI();
|
||||
scs.processSCS(1);
|
||||
scs.processPOC(new byte[]{0x01});
|
||||
|
||||
assertTrue(pd.getPageCount() >= 1);
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package haus.nightmare.lib3270j.printer;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* End-to-end tests for Phase 7 Telnet3270EP printer protocol engine, LU1 and LU3 sessions,
|
||||
* response headers, EOJ handling, listeners, and Telnet3270EPClient integration.
|
||||
*/
|
||||
public class Telnet3270EPPhase7FullTest {
|
||||
|
||||
private PrinterConfig config;
|
||||
private PD3270 pd;
|
||||
private EbcdicTranslator translator;
|
||||
private Telnet3270EP ep;
|
||||
private Telnet3270EPClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new PrinterConfig("localhost", 23, "PRT01");
|
||||
config.setAssociatedDisplayLuName("DSP01");
|
||||
config.setFormFeedAtEoj(true);
|
||||
config.setAutoFlushOnEoj(true);
|
||||
|
||||
pd = new PD3270(config);
|
||||
translator = new EbcdicTranslator("037");
|
||||
ep = new Telnet3270EP(config, pd, translator);
|
||||
client = new Telnet3270EPClient(config);
|
||||
pd.openPrinter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLU1SCSPrintSessionFlow() {
|
||||
// 1. Process BIND for LU Type 1 (SCS)
|
||||
ep.process_bind(PrinterConstants.LU_TYPE_1_SCS);
|
||||
assertEquals(PrinterConstants.LU_TYPE_1_SCS, ep.getActiveLuType());
|
||||
assertEquals(PrinterConstants.STATUS_PRINTER_READY, ep.getStatusCode());
|
||||
|
||||
// 2. Feed SCS print records
|
||||
byte[] hello = translator.stringToEbcdic("REPORT TITLE");
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(hello, 0, hello.length);
|
||||
stream.write(PrinterConstants.SCS_NL);
|
||||
byte[] data = translator.stringToEbcdic("LINE ITEM 1");
|
||||
stream.write(data, 0, data.length);
|
||||
stream.write(PrinterConstants.SCS_NL);
|
||||
|
||||
byte[] payload = stream.toByteArray();
|
||||
ep.getSCS().processHostData(payload, 0, payload.length);
|
||||
|
||||
// 3. EOJ
|
||||
ep.sendEOJ(true);
|
||||
|
||||
String text = pd.getCapturedText();
|
||||
assertTrue(text.contains("REPORT TITLE"));
|
||||
assertTrue(text.contains("LINE ITEM 1"));
|
||||
assertEquals(1, pd.getPageCount());
|
||||
assertEquals(PrinterConstants.STATUS_JOB_COMPLETE, ep.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLU33270PrintSessionFlow() {
|
||||
// 1. Process BIND for LU Type 3 (3270 Data Stream)
|
||||
ep.process_bind(PrinterConstants.LU_TYPE_3_DS);
|
||||
assertEquals(PrinterConstants.LU_TYPE_3_DS, ep.getActiveLuType());
|
||||
|
||||
// 2. Feed 3270 print DS
|
||||
byte[] hello = translator.stringToEbcdic("LU3 REPORT CONTENT");
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
stream.write(0xF5); // ERASE WRITE
|
||||
stream.write(0x00); // WCC
|
||||
stream.write(PrinterConstants.ORDER_SBA);
|
||||
stream.write(0x40);
|
||||
stream.write(0x40);
|
||||
stream.write(hello, 0, hello.length);
|
||||
|
||||
byte[] payload = stream.toByteArray();
|
||||
ep.getPrintPS().process3270PrintDS(payload, 0, payload.length);
|
||||
|
||||
// 3. EOJ
|
||||
ep.sendEOJ(true);
|
||||
|
||||
String text = pd.getCapturedText();
|
||||
assertTrue(text.contains("LU3 REPORT CONTENT"));
|
||||
assertEquals(1, pd.getPageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientFacadePDTAndChannelConfiguration() {
|
||||
PrinterDefinitionTable pcl5 = PrinterDefinitionTable.createPcl5PDT();
|
||||
client.setPDT(pcl5);
|
||||
assertEquals("PCL_5", client.getPDT().getName());
|
||||
|
||||
client.setChannelLine(4, 25);
|
||||
assertEquals(25, client.getChannelLine(4));
|
||||
|
||||
byte[] text = translator.stringToEbcdic("CLIENT CONVENIENCE TEST");
|
||||
client.printDirectBytes(text, 0, text.length);
|
||||
client.flush();
|
||||
|
||||
String captured = client.getPD().getCapturedText();
|
||||
assertTrue(captured.contains("CLIENT CONVENIENCE TEST"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrintSessionEventListeners() {
|
||||
AtomicBoolean jobStarted = new AtomicBoolean(false);
|
||||
AtomicBoolean jobComplete = new AtomicBoolean(false);
|
||||
AtomicInteger pagesReceived = new AtomicInteger(0);
|
||||
|
||||
PrintSessionListener listener = new PrintSessionListener() {
|
||||
@Override
|
||||
public void onPrintJobStarted(PrintSessionEvent event) {
|
||||
jobStarted.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrintJobData(PrintSessionEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrintJobPageComplete(PrintSessionEvent event) {
|
||||
pagesReceived.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrintJobComplete(PrintSessionEvent event) {
|
||||
jobComplete.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrinterStatusChanged(PrintSessionEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrinterError(PrintSessionEvent event) {
|
||||
}
|
||||
};
|
||||
|
||||
ep.addPrintListener(listener);
|
||||
|
||||
ep.firePrintJobStarted("JOB01");
|
||||
ep.firePrintJobPageComplete(1);
|
||||
ep.sendEOJ(true);
|
||||
|
||||
assertTrue(jobStarted.get());
|
||||
assertTrue(jobComplete.get());
|
||||
assertEquals(1, pagesReceived.get());
|
||||
|
||||
ep.removePrintListener(listener);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user