diff --git a/fiwi.py b/fiwi.py index d3ef09c..9924a43 100755 --- a/fiwi.py +++ b/fiwi.py @@ -2,11 +2,27 @@ """Fi-Wi test framework CLI — maps and SSH env files resolve to this file’s directory.""" import os +import sys import fiwi.paths as _paths _paths.configure(os.path.dirname(os.path.abspath(__file__))) + +def _prefer_line_buffered_stdio() -> None: + """Flush stdout/stderr after each newline when attached to a pipe (e.g. SSH capture).""" + for stream in (sys.stdout, sys.stderr): + reconf = getattr(stream, "reconfigure", None) + if reconf is None: + continue + try: + reconf(line_buffering=True) + except (OSError, ValueError, AttributeError): + pass + + +_prefer_line_buffered_stdio() + from fiwi.cli import main if __name__ == "__main__": diff --git a/fiwi/ssh.py b/fiwi/ssh.py index 0f18414..8798782 100644 --- a/fiwi/ssh.py +++ b/fiwi/ssh.py @@ -553,13 +553,20 @@ class SshNode: Single remote command string for ``bash -lc`` so ``~/`` in ``FIWI_REMOTE_*`` paths expand on the *remote* host (quoted tilde literals would not). - ``-u`` forces unbuffered stdout/stderr so SSH pipe capture (no TTY) still receives - BrainStem and libc output from ``discover`` and similar commands. + ``-u`` makes the interpreter unbuffered. When ``stdbuf`` from GNU coreutils is on + ``PATH`` (typical on Pi / Fedora), ``stdbuf -oL -eL`` also sets **line-buffered** + libc streams so mixed Python + native (BrainStem) output flushes on newlines over + SSH pipes (no TTY). If ``stdbuf`` is missing, the command falls back to ``-u`` only. """ py = cls.remote_path_bash_word(cfg.python) sc = cls.remote_path_bash_word(cfg.script) tail = " ".join(shlex.quote(p) for p in remote_args) - return f"{py} -u {sc}" + (f" {tail}" if tail else "") + core = f"{py} -u {sc}" + (f" {tail}" if tail else "") + return ( + "if command -v stdbuf >/dev/null 2>&1; then " + f"stdbuf -oL -eL {core}; " + f"else {core}; fi" + ) def _fiwi_cmd_argv(self, cfg: SshNodeConfig, remote_args: List[str]) -> List[str]: inner = self._fiwi_remote_shell_command(cfg, remote_args)