Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ jobs:
- run: make
- run: make test-terminals

esctest:
runs-on: ubuntu-24.04
continue-on-error: true
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y libncurses-dev python3-pexpect
- run: make
- run: git clone https://github.com/ThomasDickey/esctest2.git "$RUNNER_TEMP/esctest2"
- run: python3 test/run_esctest.py "$RUNNER_TEMP/esctest2" | tee esctest.txt
- if: always()
uses: actions/upload-artifact@v4
with:
name: esctest
path: esctest.txt

fuzz:
runs-on: ubuntu-24.04
steps:
Expand Down
65 changes: 65 additions & 0 deletions test/run_esctest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Run esctest (https://github.com/ThomasDickey/esctest2) inside dvtm.

usage: run_esctest.py /path/to/esctest2 [extra esctest.py arguments]

Prints the failing tests and a summary. Exits 0 even when tests fail, since
this is a conformance report and not a gate.
"""
import os
import re
import shlex
import sys
import tempfile
import time

import pexpect

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TIMEOUT = 1500


def main():
esctest = os.path.join(os.path.abspath(sys.argv[1]), 'esctest')
extra = sys.argv[2:] or ['--max-vt-level', '4']
tmp = tempfile.mkdtemp()
out = os.path.join(tmp, 'out')
done = os.path.join(tmp, 'done')
log = os.path.join(tmp, 'log')
shell = os.path.join(tmp, 'shell')
cmd = ' '.join(['python3', 'esctest.py', '--expected-terminal', 'xterm',
'--logfile', shlex.quote(log), '--no-print-logs'] +
[shlex.quote(a) for a in extra])
with open(shell, 'w') as f:
f.write(f'#!/bin/sh\ncd {shlex.quote(esctest)}\n'
f'{cmd} 2> {shlex.quote(out)}\n'
f'touch {shlex.quote(done)}\nsleep 60\n')
os.chmod(shell, 0o755)

env = dict(os.environ, SHELL=shell, TERM='xterm')
p = pexpect.spawn(os.path.join(ROOT, 'dvtm'), env=env, dimensions=(25, 80))
start = time.time()
while not os.path.exists(done) and time.time() - start < TIMEOUT:
try:
p.read_nonblocking(65536, 0.5)
except (pexpect.TIMEOUT, pexpect.EOF):
pass
p.terminate(force=True)

if not os.path.exists(done):
print('esctest did not finish')
return 0
with open(out, errors='replace') as f:
text = f.read()
with open(log, errors='replace') as f:
failed = re.findall(r'^\*\*\* TEST (\S+) FAILED', f.read(), re.M)
for name in failed:
print('FAILED', name)
if not failed:
print(text[-2000:])
print(f'{len(failed)} test(s) failed')
return 0


if __name__ == '__main__':
sys.exit(main())
22 changes: 22 additions & 0 deletions test/test-vt.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,27 @@ static void test_device_attributes(void)
}
}

static void test_text_area_size(void)
{
term = "XTWINOPS";
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds))
abort();
Vt *t = vt_create(5, 10, 0);
t->pty = fds[0];
const char *query = "\033[18t\033[14t";
if (write(fds[1], query, strlen(query)) != (ssize_t)strlen(query))
abort();
vt_process(t);
fcntl(fds[1], F_SETFL, O_NONBLOCK);
char buf[32] = "";
ssize_t len = read(fds[1], buf, sizeof buf - 1);
buf[len > 0 ? len : 0] = '\0';
CHECK(!strcmp(buf, "\033[8;5;10t"));
close(fds[1]);
vt_destroy(t);
}

int main(void)
{
setlocale(LC_CTYPE, "C.UTF-8");
Expand Down Expand Up @@ -191,6 +212,7 @@ int main(void)
setenv("TERMINFO", dir, 1);

test_device_attributes();
test_text_area_size();
test_term("xterm", COLOR_RED, COLOR_RED, COLOR_BLACK, COLOR_BLACK);
test_term("xterm-256color", 196, 196, 16, 16);
if (direct) {
Expand Down
7 changes: 7 additions & 0 deletions vt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,13 @@ static void interpret_csi(Vt *t)
if (param_count == 1 && csiparam[0] == 6)
send_curs(t);
break;
case 't': /* report text area size in characters */
if (param_count == 1 && csiparam[0] == 18) {
char reply[32];
snprintf(reply, sizeof reply, "\e[8;%d;%dt", t->buffer->rows, t->buffer->cols);
vt_write(t, reply, strlen(reply));
}
break;
case 'c': /* primary device attributes, answer as a VT100 with advanced video */
if (t->ebuf[1] != '>' && t->ebuf[1] != '=' && (param_count == 0 || csiparam[0] == 0))
vt_write(t, "[?1;2c", 7);
Expand Down
Loading