From 5858baf199f0ca49a642f18b0ef27b50386b180f Mon Sep 17 00:00:00 2001 From: Dmitry Konstantinov Date: Mon, 22 Jun 2026 12:16:56 +0100 Subject: [PATCH] run_cqlsh.py: fix parsing of a multi-byte UTF-8 sequence, os.read()/pipe reads can split the sequence across two reads patch by Dmitry Konstantinov; reviewed by Stefan Miklosovic for CASSANDRA-21466 --- pylib/cqlshlib/test/run_cqlsh.py | 33 ++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/pylib/cqlshlib/test/run_cqlsh.py b/pylib/cqlshlib/test/run_cqlsh.py index 288d3ed951..507e4d5337 100644 --- a/pylib/cqlshlib/test/run_cqlsh.py +++ b/pylib/cqlshlib/test/run_cqlsh.py @@ -19,6 +19,7 @@ import os import sys import re +import codecs import contextlib import subprocess import signal @@ -120,6 +121,12 @@ class ProcRunner: env = {} self.env = env self.readbuf = '' + # os.read()/pipe reads can split a multi-byte UTF-8 sequence across two + # reads (the boundary depends on terminal/readline echo flushing, which + # changed with the Ubuntu 22.04 / readline 8.1 build image). Decode + # incrementally so a partial trailing sequence is buffered until the + # rest of its bytes arrive, instead of failing with UnicodeDecodeError. + self._decoder = codecs.getincrementaldecoder("utf-8")() self.start_proc() @@ -166,16 +173,26 @@ class ProcRunner: self.proc.stdin.write(data) def read_tty(self, blksize, timeout=None): - buf = os.read(self.childpty, blksize) - if isinstance(buf, bytes): - buf = buf.decode("utf-8") - return buf + while True: + buf = os.read(self.childpty, blksize) + if not isinstance(buf, bytes): + return buf + decoded = self._decoder.decode(buf) + # A non-empty read that decodes to '' means we only got the start of + # a multi-byte UTF-8 sequence; keep reading until it completes rather + # than returning '', which callers (read_until) treat as EOF. + # An empty read (b'') is genuine EOF and must propagate as ''. + if decoded != '' or buf == b'': + return decoded def read_pipe(self, blksize, timeout=None): - buf = self.proc.stdout.read(blksize) - if isinstance(buf, bytes): - buf = buf.decode("utf-8") - return buf + while True: + buf = self.proc.stdout.read(blksize) + if not isinstance(buf, bytes): + return buf + decoded = self._decoder.decode(buf) + if decoded != '' or buf == b'': + return decoded def read_until(self, until, blksize=4096, timeout=None, flags=0, ptty_timeout=None, replace=[]):