Skip to content
Snippets Groups Projects
Commit 6560904b authored by George Nachman's avatar George Nachman Committed by George Nachman
Browse files

Refactor the CSI parser and add a suite of automatic unit tests to measure xterm compatibility.

parent 66065314
No related branches found
No related tags found
No related merge requests found
Showing
with 1907 additions and 0 deletions
from esc import NUL, blank
import escargs
import esccmd
import escio
from esctypes import Point, Rect
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, knownBug
class EDTests(object):
def prepare(self):
"""Sets up the display as:
a
bcd
e
With the cursor on the 'c'.
"""
esccmd.CUP(Point(1, 1))
escio.Write("a")
esccmd.CUP(Point(1, 3))
escio.Write("bcd")
esccmd.CUP(Point(1, 5))
escio.Write("e")
esccmd.CUP(Point(2, 3))
def prepare_wide(self):
"""Sets up the display as:
abcde
fghij
klmno
With the cursor on the 'h'.
"""
esccmd.CUP(Point(1, 1))
escio.Write("abcde")
esccmd.CUP(Point(1, 2))
escio.Write("fghij")
esccmd.CUP(Point(1, 3))
escio.Write("klmno")
esccmd.CUP(Point(2, 3))
def test_ED_Default(self):
"""Should be the same as ED_0."""
self.prepare()
esccmd.ED()
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 5),
[ "a" + NUL * 2,
NUL * 3,
"b" + NUL * 2,
NUL * 3,
NUL * 3 ])
def test_ED_0(self):
"""Erase after cursor."""
self.prepare()
esccmd.ED(0)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 5),
[ "a" + NUL * 2,
NUL * 3,
"b" + NUL * 2,
NUL * 3,
NUL * 3 ])
def test_ED_1(self):
"""Erase before cursor."""
self.prepare()
esccmd.ED(1)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 5),
[ NUL * 3,
NUL * 3,
blank() * 2 + "d",
NUL * 3,
"e" + NUL * 2 ])
def test_ED_2(self):
"""Erase whole screen."""
self.prepare()
esccmd.ED(2)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 5),
[ NUL * 3,
NUL * 3,
NUL * 3,
NUL * 3,
NUL * 3 ])
def test_ED_3(self):
"""xterm supports a "3" parameter, which also erases scrollback history. There
is no way to test if it's working, though. We can at least test that it doesn't
touch the screen."""
self.prepare()
esccmd.ED(3)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 5),
[ "a" + NUL * 2,
NUL * 3,
"bcd",
NUL * 3,
"e" + NUL * 2 ])
def test_ED_0_WithScrollRegion(self):
"""Erase after cursor with a scroll region present. The scroll region is ignored."""
self.prepare_wide()
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 4)
esccmd.DECSTBM(2, 3)
esccmd.CUP(Point(3, 2))
esccmd.ED(0)
esccmd.DECRESET(esccmd.DECLRMM)
esccmd.DECSTBM()
AssertScreenCharsInRectEqual(Rect(1, 1, 5, 3),
[ "abcde",
"fg" + NUL * 3,
NUL * 5 ])
def test_ED_1_WithScrollRegion(self):
"""Erase before cursor with a scroll region present. The scroll region is ignored."""
self.prepare_wide()
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 4)
esccmd.DECSTBM(2, 3)
esccmd.CUP(Point(3, 2))
esccmd.ED(1)
esccmd.DECRESET(esccmd.DECLRMM)
esccmd.DECSTBM()
AssertScreenCharsInRectEqual(Rect(1, 1, 5, 3),
[ NUL * 5,
blank() * 3 + "ij",
"klmno" ])
def test_ED_2_WithScrollRegion(self):
"""Erase whole screen with a scroll region present. The scroll region is ignored."""
self.prepare_wide()
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 4)
esccmd.DECSTBM(2, 3)
esccmd.CUP(Point(3, 2))
esccmd.ED(2)
esccmd.DECRESET(esccmd.DECLRMM)
esccmd.DECSTBM()
AssertScreenCharsInRectEqual(Rect(1, 1, 5, 3),
[ NUL * 5,
NUL * 5,
NUL * 5 ])
def test_ED_doesNotRespectDECProtection(self):
"""ED should not respect DECSCA"""
escio.Write("a")
escio.Write("b")
esccmd.DECSCA(1)
escio.Write("c")
esccmd.DECSCA(0)
esccmd.CUP(Point(1, 1))
esccmd.ED(0)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 1),
[ NUL * 3 ])
@knownBug(terminal="iTerm2",
reason="Protection not implemented.")
def test_ED_respectsISOProtection(self):
"""ED respects SPA/EPA."""
escio.Write("a")
escio.Write("b")
esccmd.SPA()
escio.Write("c")
esccmd.EPA()
esccmd.CUP(Point(1, 1))
esccmd.ED(0)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 1),
[ blank() * 2 + "c" ])
from esc import NUL, blank
import escargs
import esccmd
import escio
from esctypes import Point, Rect
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, knownBug
class ELTests(object):
def prepare(self):
"""Initializes the screen to abcdefghij on the first line with the cursor
on the 'e'."""
esccmd.CUP(Point(1, 1))
escio.Write("abcdefghij")
esccmd.CUP(Point(5, 1))
def test_EL_Default(self):
"""Should erase to right of cursor."""
self.prepare()
esccmd.EL()
AssertScreenCharsInRectEqual(Rect(1, 1, 10, 1),
[ "abcd" + 6 * NUL ])
def test_EL_0(self):
"""Should erase to right of cursor."""
self.prepare()
esccmd.EL(0)
AssertScreenCharsInRectEqual(Rect(1, 1, 10, 1),
[ "abcd" + 6 * NUL ])
def test_EL_1(self):
"""Should erase to left of cursor."""
self.prepare()
esccmd.EL(1)
AssertScreenCharsInRectEqual(Rect(1, 1, 10, 1),
[ 5 * blank() + "fghij" ])
def test_EL_2(self):
"""Should erase whole line."""
self.prepare()
esccmd.EL(2)
AssertScreenCharsInRectEqual(Rect(1, 1, 10, 1),
[ 10 * NUL ])
def test_EL_IgnoresScrollRegion(self):
"""Should erase whole line."""
self.prepare()
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 4)
esccmd.CUP(Point(5, 1))
esccmd.EL(2)
esccmd.DECRESET(esccmd.DECLRMM)
AssertScreenCharsInRectEqual(Rect(1, 1, 10, 1),
[ 10 * NUL ])
def test_EL_doesNotRespectDECProtection(self):
"""EL respects DECSCA."""
escio.Write("a")
escio.Write("b")
esccmd.DECSCA(1)
escio.Write("c")
esccmd.DECSCA(0)
esccmd.CUP(Point(1, 1))
esccmd.EL(2)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 1),
[ NUL * 3 ])
@knownBug(terminal="iTerm2",
reason="Protection not implemented.")
def test_EL_respectsISOProtection(self):
"""EL respects SPA/EPA."""
escio.Write("a")
escio.Write("b")
esccmd.SPA()
escio.Write("c")
esccmd.EPA()
esccmd.CUP(Point(1, 1))
esccmd.EL(2)
AssertScreenCharsInRectEqual(Rect(1, 1, 3, 1),
[ blank() * 2 + "c" ])
from esc import FF, NUL
import esccmd
import escio
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, GetScreenSize, knownBug
from esctypes import Point, Rect
class FFTests(object):
"""LNM is tested in the SM tests, and not duplicated here. These tests are
the same as those for IND."""
def test_FF_Basic(self):
"""FF moves the cursor down one line."""
esccmd.CUP(Point(5, 3))
escio.Write(FF)
position = GetCursorPosition()
AssertEQ(position.x(), 5)
AssertEQ(position.y(), 4)
def test_FF_Scrolls(self):
"""FF scrolls when it hits the bottom."""
height = GetScreenSize().height()
# Put a and b on the last two lines.
esccmd.CUP(Point(2, height - 1))
escio.Write("a")
esccmd.CUP(Point(2, height))
escio.Write("b")
# Move to penultimate line.
esccmd.CUP(Point(2, height - 1))
# Move down, ensure no scroll yet.
escio.Write(FF)
AssertEQ(GetCursorPosition().y(), height)
AssertScreenCharsInRectEqual(Rect(2, height - 2, 2, height), [ NUL, "a", "b" ])
# Move down, ensure scroll.
escio.Write(FF)
AssertEQ(GetCursorPosition().y(), height)
AssertScreenCharsInRectEqual(Rect(2, height - 2, 2, height), [ "a", "b", NUL ])
def test_FF_ScrollsInTopBottomRegionStartingAbove(self):
"""FF scrolls when it hits the bottom region (starting above top)."""
esccmd.DECSTBM(4, 5)
esccmd.CUP(Point(2, 5))
escio.Write("x")
esccmd.CUP(Point(2, 3))
escio.Write(FF)
escio.Write(FF)
escio.Write(FF)
AssertEQ(GetCursorPosition(), Point(2, 5))
AssertScreenCharsInRectEqual(Rect(2, 4, 2, 5), [ "x", NUL ])
def test_FF_ScrollsInTopBottomRegionStartingWithin(self):
"""FF scrolls when it hits the bottom region (starting within region)."""
esccmd.DECSTBM(4, 5)
esccmd.CUP(Point(2, 5))
escio.Write("x")
esccmd.CUP(Point(2, 4))
escio.Write(FF)
escio.Write(FF)
AssertEQ(GetCursorPosition(), Point(2, 5))
AssertScreenCharsInRectEqual(Rect(2, 4, 2, 5), [ "x", NUL ])
@knownBug(terminal="iTerm2",
reason="iTerm2 improperly scrolls when the cursor is outside the left-right region.")
def test_FF_MovesDoesNotScrollOutsideLeftRight(self):
"""Cursor moves down but won't scroll when outside left-right region."""
esccmd.DECSTBM(2, 5)
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 5)
esccmd.CUP(Point(3, 5))
escio.Write("x")
# Move past bottom margin but to the right of the left-right region
esccmd.CUP(Point(6, 5))
escio.Write(FF)
# Cursor won't pass bottom or scroll.
AssertEQ(GetCursorPosition(), Point(6, 5))
AssertScreenCharsInRectEqual(Rect(3, 5, 3, 5), [ "x" ])
# Try to move past the bottom of the screen but to the right of the left-right region
height = GetScreenSize().height()
esccmd.CUP(Point(6, height))
escio.Write(FF)
AssertEQ(GetCursorPosition(), Point(6, height))
AssertScreenCharsInRectEqual(Rect(3, 5, 3, 5), [ "x" ])
# Move past bottom margin but to the left of the left-right region
esccmd.CUP(Point(1, 5))
escio.Write(FF)
AssertEQ(GetCursorPosition(), Point(1, 5))
AssertScreenCharsInRectEqual(Rect(3, 5, 3, 5), [ "x" ])
# Try to move past the bottom of the screen but to the left of the left-right region
height = GetScreenSize().height()
esccmd.CUP(Point(1, height))
escio.Write(FF)
AssertEQ(GetCursorPosition(), Point(1, height))
AssertScreenCharsInRectEqual(Rect(3, 5, 3, 5), [ "x" ])
def test_FF_StopsAtBottomLineWhenBegunBelowScrollRegion(self):
"""When the cursor starts below the scroll region, index moves it down to the
bottom of the screen but won't scroll."""
# Set a scroll region. This must be done first because DECSTBM moves the cursor to the origin.
esccmd.DECSTBM(4, 5)
# Position the cursor below the scroll region
esccmd.CUP(Point(1, 6))
escio.Write("x")
# Move it down by a lot
height = GetScreenSize().height()
for i in xrange(height):
escio.Write(FF)
# Ensure it stopped at the bottom of the screen
AssertEQ(GetCursorPosition().y(), height)
# Ensure no scroll
AssertScreenCharsInRectEqual(Rect(1, 6, 1, 6), [ "x" ])
from esc import CR, LF
import esccmd
import escio
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, GetScreenSize, Rect, knownBug, vtLevel
from esctypes import Point
class FillRectangleTests(object):
def data(self):
return [ "abcdefgh",
"ijklmnop",
"qrstuvwx",
"yz012345",
"ABCDEFGH",
"IJKLMNOP",
"QRSTUVWX",
"YZ6789!@" ]
def prepare(self):
esccmd.CUP(Point(1, 1))
for line in self.data():
escio.Write(line + CR + LF)
def fill(self, top=None, left=None, bottom=None, right=None):
"""Subclasses should override this to do the appropriate fill action."""
pass
def characters(self, point, count):
"""Returns the filled characters starting at point, and count of them."""
return "!" * count
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented")
def fillRectangle_basic(self):
self.prepare()
self.fill(top=5,
left=5,
bottom=7,
right=7)
AssertScreenCharsInRectEqual(Rect(1, 1, 8, 8),
[ "abcdefgh",
"ijklmnop",
"qrstuvwx",
"yz012345",
"ABCD" + self.characters(Point(5, 5), 3) + "H",
"IJKL" + self.characters(Point(5, 6), 3) + "P",
"QRST" + self.characters(Point(5, 7), 3) + "X",
"YZ6789!@" ])
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented", noop=True)
def fillRectangle_invalidRectDoesNothing(self):
self.prepare()
self.fill(top=5,
left=5,
bottom=4,
right=4)
AssertScreenCharsInRectEqual(Rect(1, 1, 8, 8),
[ "abcdefgh",
"ijklmnop",
"qrstuvwx",
"yz012345",
"ABCDEFGH",
"IJKLMNOP",
"QRSTUVWX",
"YZ6789!@" ])
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented")
def fillRectangle_defaultArgs(self):
"""Write a value at each corner, run fill with no args, and verify the
corners have all been replaced with self.character."""
size = GetScreenSize()
points = [ Point(1, 1),
Point(size.width(), 1),
Point(size.width(), size.height()),
Point(1, size.height()) ]
n = 1
for point in points:
esccmd.CUP(point)
escio.Write(str(n))
n += 1
self.fill()
for point in points:
AssertScreenCharsInRectEqual(
Rect(point.x(), point.y(), point.x(), point.y()),
[ self.characters(point, 1) ])
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented")
def fillRectangle_respectsOriginMode(self):
self.prepare()
# Set margins starting at 2 and 2
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(2, 9)
esccmd.DECSTBM(2, 9)
# Turn on origin mode
esccmd.DECSET(esccmd.DECOM)
# Fill from 1,1 to 3,3 - with origin mode, that's 2,2 to 4,4
self.fill(top=1,
left=1,
bottom=3,
right=3)
# Turn off margins and origin mode
esccmd.DECRESET(esccmd.DECLRMM)
esccmd.DECSTBM()
esccmd.DECRESET(esccmd.DECOM)
# See what happened.
AssertScreenCharsInRectEqual(Rect(1, 1, 8, 8),
[ "abcdefgh",
"i" + self.characters(Point(2, 2), 3) + "mnop",
"q" + self.characters(Point(2, 3), 3) + "uvwx",
"y" + self.characters(Point(2, 4), 3) + "2345",
"ABCDEFGH",
"IJKLMNOP",
"QRSTUVWX",
"YZ6789!@" ])
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented")
def fillRectangle_overlyLargeSourceClippedToScreenSize(self):
size = GetScreenSize()
# Put ab, cX in the bottom right
esccmd.CUP(Point(size.width() - 1, size.height() - 1))
escio.Write("ab")
esccmd.CUP(Point(size.width() - 1, size.height()))
escio.Write("cd")
# Fill a 2x2 block starting at the d.
self.fill(top=size.height(),
left=size.width(),
bottom=size.height() + 10,
right=size.width() + 10)
AssertScreenCharsInRectEqual(Rect(size.width() - 1,
size.height() - 1,
size.width(),
size.height()),
[ "ab",
"c" + self.characters(Point(size.width(), size.height()), 1) ])
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented", noop=True)
def fillRectangle_cursorDoesNotMove(self):
# Make sure something is on screen (so the test is more deterministic)
self.prepare()
# Place the cursor
position = Point(3, 4)
esccmd.CUP(position)
# Fill a block
self.fill(top=2,
left=2,
bottom=4,
right=4)
# Make sure the cursor is where we left it.
AssertEQ(GetCursorPosition(), position)
@vtLevel(4)
@knownBug(terminal="iTerm2", reason="Not implemented")
def fillRectangle_ignoresMargins(self):
self.prepare()
# Set margins
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(3, 6)
esccmd.DECSTBM(3, 6)
# Fill!
self.fill(top=5,
left=5,
bottom=7,
right=7)
# Remove margins
esccmd.DECRESET(esccmd.DECLRMM)
esccmd.DECSTBM()
# Did it ignore the margins?
AssertScreenCharsInRectEqual(Rect(1, 1, 8, 8),
[ "abcdefgh",
"ijklmnop",
"qrstuvwx",
"yz012345",
"ABCD" + self.characters(Point(5, 5), 3) + "H",
"IJKL" + self.characters(Point(5, 6), 3) + "P",
"QRST" + self.characters(Point(5, 7), 3) + "X",
"YZ6789!@" ])
import esccmd
import escio
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, GetScreenSize, knownBug
from esctypes import Point, Rect
class HPATests(object):
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPA_DefaultParams(self):
"""With no params, HPA moves to 1st column."""
esccmd.HPA(6)
position = GetCursorPosition()
AssertEQ(position.x(), 6)
esccmd.HPA()
position = GetCursorPosition()
AssertEQ(position.x(), 1)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPA_StopsAtRightEdge(self):
"""HPA won't go past the right edge."""
# Position on 6th row
esccmd.CUP(Point(5, 6))
# Try to move 10 past the right edge
size = GetScreenSize()
esccmd.HPA(size.width() + 10)
# Ensure at the right edge on same row
position = GetCursorPosition()
AssertEQ(position.x(), size.width())
AssertEQ(position.y(), 6)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPA_DoesNotChangeRow(self):
"""HPA moves the specified column and does not change the row."""
esccmd.CUP(Point(5, 6))
esccmd.HPA(2)
position = GetCursorPosition()
AssertEQ(position.x(), 2)
AssertEQ(position.y(), 6)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPA_IgnoresOriginMode(self):
"""HPA does not respect origin mode."""
# Set a scroll region.
esccmd.DECSTBM(6, 11)
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(5, 10)
# Move to center of region
esccmd.CUP(Point(7, 9))
position = GetCursorPosition()
AssertEQ(position.x(), 7)
AssertEQ(position.y(), 9)
# Turn on origin mode.
esccmd.DECSET(esccmd.DECOM)
# Move to 2nd column
esccmd.HPA(2)
position = GetCursorPosition()
AssertEQ(position.x(), 2)
from esc import NUL
import esccmd
import escio
from escutil import AssertEQ, AssertScreenCharsInRectEqual, GetCursorPosition, GetScreenSize, knownBug
from esctypes import Point, Rect
class HPRTests(object):
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPR_DefaultParams(self):
"""With no params, HPR moves right by 1."""
esccmd.CUP(Point(6, 1))
esccmd.HPR()
position = GetCursorPosition()
AssertEQ(position.x(), 7)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPR_StopsAtRightEdge(self):
"""HPR won't go past the right edge."""
# Position on 6th row
esccmd.CUP(Point(5, 6))
# Try to move 10 past the right edge
size = GetScreenSize()
esccmd.HPR(size.width() + 10)
# Ensure at the right edge on same row
position = GetCursorPosition()
AssertEQ(position.x(), size.width())
AssertEQ(position.y(), 6)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPR_DoesNotChangeRow(self):
"""HPR moves the specified column and does not change the row."""
esccmd.CUP(Point(5, 6))
esccmd.HPR(2)
position = GetCursorPosition()
AssertEQ(position.x(), 7)
AssertEQ(position.y(), 6)
@knownBug(terminal="iTerm2", reason="Not implemented")
def test_HPR_IgnoresOriginMode(self):
"""HPR continues to work in origin mode."""
# Set a scroll region.
esccmd.DECSTBM(6, 11)
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSLRM(5, 10)
# Enter origin mode
esccmd.DECSET(esccmd.DECOM)
# Move to center of region
esccmd.CUP(Point(2, 2))
escio.Write('X')
# Move right by 2
esccmd.HPR(2)
escio.Write('Y')
# Exit origin mode
esccmd.DECRESET(esccmd.DECOM)
# Reset margins
esccmd.DECSET(esccmd.DECLRMM)
esccmd.DECSTBM()
# See what happened
AssertScreenCharsInRectEqual(Rect(5, 7, 9, 7), [ NUL + "X" + NUL * 2 + "Y" ])
from esc import TAB, S8C1T, S7C1T
import escargs
import esccmd
import escio
from escutil import AssertEQ, GetCursorPosition, knownBug, optionRequired
from esctypes import Point
class HTSTests(object):
def test_HTS_Basic(self):
# Remove tabs
esccmd.TBC(3)
# Set a tabstop at 20
esccmd.CUP(Point(20, 1))
esccmd.HTS()
# Move to 1 and then tab to 20
esccmd.CUP(Point(1, 1))
escio.Write(TAB)
AssertEQ(GetCursorPosition().x(), 20)
@optionRequired(terminal="xterm", option=escargs.DISABLE_WIDE_CHARS)
@knownBug(terminal="iTerm2", reason="8-bit controls not implemented.")
def test_HTS_8bit(self):
# Remove tabs
esccmd.TBC(3)
# Set a tabstop at 20
esccmd.CUP(Point(20, 1))
# Do 8 bit hts
escio.use8BitControls = True
escio.Write(S8C1T)
esccmd.HTS()
escio.Write(S7C1T)
escio.use8BitControls = False
# Move to 1 and then tab to 20
esccmd.CUP(Point(1, 1))
escio.Write(TAB)
AssertEQ(GetCursorPosition().x(), 20)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment