tests/functional2: add terminal code eater
I want this for being able to write reasonable expect-test style tests
for oneliners. We will still probably want something like insta for more
complicated test cases where you actually *want* the output in a
different file, but for now this will do.
cc: #595
Change-Id: I6ddc42963cc49177762cfca206fe9a9efe1ae65d
This commit is contained in:
parent
93d5221b9b
commit
c0808bd855
|
@ -4,6 +4,7 @@ import subprocess
|
|||
from typing import Any
|
||||
from pathlib import Path
|
||||
from functools import partial, partialmethod
|
||||
from functional2.testlib.terminal_code_eater import eat_terminal_codes
|
||||
import dataclasses
|
||||
|
||||
|
||||
|
@ -33,6 +34,26 @@ class CommandResult:
|
|||
output=self.stdout)
|
||||
return self
|
||||
|
||||
@property
|
||||
def stdout_s(self) -> str:
|
||||
"""Command stdout as str"""
|
||||
return self.stdout.decode('utf-8', errors='replace')
|
||||
|
||||
@property
|
||||
def stderr_s(self) -> str:
|
||||
"""Command stderr as str"""
|
||||
return self.stderr.decode('utf-8', errors='replace')
|
||||
|
||||
@property
|
||||
def stdout_plain(self) -> str:
|
||||
"""Command stderr as str with terminal escape sequences eaten and whitespace stripped"""
|
||||
return eat_terminal_codes(self.stdout).decode('utf-8', errors='replace').strip()
|
||||
|
||||
@property
|
||||
def stderr_plain(self) -> str:
|
||||
"""Command stderr as str with terminal escape sequences eaten and whitespace stripped"""
|
||||
return eat_terminal_codes(self.stderr).decode('utf-8', errors='replace').strip()
|
||||
|
||||
def json(self) -> Any:
|
||||
self.ok()
|
||||
return json.loads(self.stdout)
|
||||
|
|
76
tests/functional2/testlib/terminal_code_eater.py
Normal file
76
tests/functional2/testlib/terminal_code_eater.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
# copy pasta from libutil-support's terminal-code-eater.cc
|
||||
|
||||
import enum
|
||||
import dataclasses
|
||||
|
||||
|
||||
class State(enum.Enum):
|
||||
ExpectESC = 1
|
||||
ExpectESCSeq = 2
|
||||
InCSIParams = 3
|
||||
InCSIIntermediates = 4
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class TerminalCodeEater:
|
||||
state: State = State.ExpectESC
|
||||
|
||||
def feed(self, data: bytes) -> bytes:
|
||||
is_param_char = lambda c: c >= 0x30 and c <= 0x3f
|
||||
is_intermediate_char = lambda c: c >= 0x20 and c <= 0x2f
|
||||
is_final_char = lambda c: c >= 0x40 and c <= 0x7e
|
||||
|
||||
ret = bytearray()
|
||||
for c in data:
|
||||
match self.state:
|
||||
case State.ExpectESC:
|
||||
match c:
|
||||
case 0x1b: # \e
|
||||
self._transition(State.ExpectESCSeq)
|
||||
continue
|
||||
case 0xd: # \r
|
||||
continue
|
||||
ret.append(c)
|
||||
case State.ExpectESCSeq:
|
||||
match c:
|
||||
# CSI ('[')
|
||||
case 0x5b:
|
||||
self._transition(State.InCSIParams)
|
||||
continue
|
||||
# FIXME(jade): whatever this was, we do not know how to
|
||||
# delimit it, so we just eat the next character and
|
||||
# keep going. Should we actually eat it?
|
||||
case _:
|
||||
self._transition(State.ExpectESC)
|
||||
continue
|
||||
# https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
|
||||
# A CSI sequence is: CSI [\x30-\x3f]* [\x20-\x2f]* [\x40-\x7e]
|
||||
# ^ params ^ intermediates ^ final byte
|
||||
case State.InCSIParams:
|
||||
if is_final_char(c):
|
||||
self._transition(State.ExpectESC)
|
||||
continue
|
||||
elif is_intermediate_char(c):
|
||||
self._transition(State.InCSIIntermediates)
|
||||
continue
|
||||
elif is_param_char(c):
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f'Corrupt escape sequence, at {c:x}')
|
||||
case State.InCSIIntermediates:
|
||||
if is_final_char(c):
|
||||
self._transition(State.ExpectESC)
|
||||
continue
|
||||
elif is_intermediate_char(c):
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f'Corrupt escape sequence in intermediates, at {c:x}')
|
||||
|
||||
return bytes(ret)
|
||||
|
||||
def _transition(self, new_state: State):
|
||||
self.state = new_state
|
||||
|
||||
|
||||
def eat_terminal_codes(s: bytes) -> bytes:
|
||||
return TerminalCodeEater().feed(s)
|
|
@ -1,3 +1,4 @@
|
|||
// this file has a hissing snake twin in functional2/testlib/terminal_code_eater.py
|
||||
#include "terminal-code-eater.hh"
|
||||
#include "lix/libutil/escape-char.hh"
|
||||
#include <assert.h>
|
||||
|
@ -39,6 +40,8 @@ void TerminalCodeEater::feed(char c, std::function<void(char)> on_char)
|
|||
case '[':
|
||||
transition(State::InCSIParams);
|
||||
return;
|
||||
// FIXME(jade): whatever this was, we do not know how to delimit it, so
|
||||
// we just eat the next character and keep going
|
||||
default:
|
||||
transition(State::ExpectESC);
|
||||
return;
|
||||
|
|
Loading…
Reference in a new issue