[llvm] [lit] Add stream-injectable run() core to builtin cat (PR #204711)

Prasoon Kumar via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 29 18:20:08 PDT 2026


https://github.com/prasoon054 updated https://github.com/llvm/llvm-project/pull/204711

>From ce2061dd68b8b0e5ea5b4dcb3e3b27e2ac196eb2 Mon Sep 17 00:00:00 2001
From: Prasoon Kumar <prasoonkumar054 at gmail.com>
Date: Fri, 19 Jun 2026 06:51:04 +0530
Subject: [PATCH] [lit] Add stream-injectable run() core to builtin cat

Pull cat's logic out into run(argv, stdin, stdout, stderr, cwd) so it
takes explicit streams instead of touching sys.std* directly. main()
just calls run() with the real process streams, so nothing changes
for the spawned-script path.

Needed before cat can run in-process inside the lit worker

Also switched file reads to raw bytes throughout, since the old
text-mode read + win32 msvcrt.setmode was only there for sys.stdout's
encoding, which doesn't apply once we pass in a binary stream
directly. Error messages still report the original filename, not the
cwd-joined path.

Signed-off-by: Prasoon Kumar <prasoonkumar054 at gmail.com>
---
 llvm/utils/lit/lit/builtin_commands/cat.py | 74 ++++++++++++----------
 1 file changed, 40 insertions(+), 34 deletions(-)

diff --git a/llvm/utils/lit/lit/builtin_commands/cat.py b/llvm/utils/lit/lit/builtin_commands/cat.py
index 2797e0cbb4154..fc7d1229bc674 100644
--- a/llvm/utils/lit/lit/builtin_commands/cat.py
+++ b/llvm/utils/lit/lit/builtin_commands/cat.py
@@ -1,4 +1,5 @@
 import getopt
+import os
 import sys
 from io import StringIO
 
@@ -26,7 +27,23 @@ def convertToCaretAndMNotation(data):
     return newdata.getvalue().encode()
 
 
-def main(argv):
+def run(argv, stdin, stdout, stderr, cwd):
+    """In-process cat.
+
+    Since it never calls sys.exit, this is safe to run inside the lit worker as well as
+    from the standalone main below.
+
+    Args:
+        argv: A list of command-line arguments. The first element is the command name,
+            followed by options or filenames.
+        stdin: Binary input stream used if no files are specified.
+        stdout: Binary output stream for the concatenated content.
+        stderr: Binary error stream for error messages.
+        cwd: The shell's current working directory, used to resolve relative file paths.
+
+    Returns:
+        An integer representing the exit code (0 for success, 1 for errors).
+    """
     arguments = argv[1:]
     short_options = "v"
     long_options = ["show-nonprinting"]
@@ -35,49 +52,38 @@ def main(argv):
     try:
         options, filenames = getopt.gnu_getopt(arguments, short_options, long_options)
     except getopt.GetoptError as err:
-        sys.stderr.write("Unsupported: 'cat':  %s\n" % str(err))
-        sys.exit(1)
+        stderr.write(b"Unsupported: 'cat':  %s\n" % str(err).encode())
+        return 1
 
     for option, value in options:
         if option == "-v" or option == "--show-nonprinting":
             show_nonprinting = True
 
-    writer = getattr(sys.stdout, "buffer", None)
-    if writer is None:
-        writer = sys.stdout
-        if sys.platform == "win32":
-            import os, msvcrt
-
-            msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
     if len(filenames) == 0:
-        sys.stdout.write(sys.stdin.read())
-        sys.exit(0)
+        stdout.write(stdin.read())
+        return 0
+
     for filename in filenames:
+        path = filename
+        contents = None
+        if not os.path.isabs(path):
+            path = os.path.join(cwd, path)
         try:
-            contents = None
-            is_text = False
-            try:
-                if sys.platform != "win32":
-                    fileToCat = open(filename, "r")
-                    contents = fileToCat.read()
-                    is_text = True
-            except:
-                pass
-
-            if contents is None:
-                fileToCat = open(filename, "rb")
+            with open(path, "rb") as fileToCat:
                 contents = fileToCat.read()
-
-            if show_nonprinting:
-                contents = convertToCaretAndMNotation(contents)
-            elif is_text:
-                contents = contents.encode()
-            writer.write(contents)
-            sys.stdout.flush()
-            fileToCat.close()
         except IOError as error:
-            sys.stderr.write(str(error))
-            sys.exit(1)
+            error.filename = filename
+            stderr.write(str(error).encode())
+            return 1
+        if show_nonprinting:
+            contents = convertToCaretAndMNotation(contents)
+        stdout.write(contents)
+    return 0
+
+
+def main(argv):
+    out = getattr(sys.stdout, "buffer", sys.stdout)
+    sys.exit(run(argv, sys.stdin.buffer, out, sys.stderr.buffer, os.getcwd()))
 
 
 if __name__ == "__main__":



More information about the llvm-commits mailing list