xref: /aosp_15_r20/external/cronet/testing/scripts/rust/exe_util.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2021 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Utilities for invoking executables.
5"""
6
7import os
8import subprocess
9import re
10import sys
11
12# Regex for matching 7-bit and 8-bit C1 ANSI sequences.
13# Credit: https://stackoverflow.com/a/14693789/4692014
14_ANSI_ESCAPE_8BIT_REGEX = re.compile(
15    r"""
16    (?: # either 7-bit C1, two bytes, ESC Fe (omitting CSI)
17        \x1B
18        [@-Z\\-_]
19    |   # or a single 8-bit byte Fe (omitting CSI)
20        [\x80-\x9A\x9C-\x9F]
21    |   # or CSI + control codes
22        (?: # 7-bit CSI, ESC [
23            \x1B\[
24        |   # 8-bit CSI, 9B
25            \x9B
26        )
27        [0-?]*  # Parameter bytes
28        [ -/]*  # Intermediate bytes
29        [@-~]   # Final byte
30    )
31""", re.VERBOSE)
32
33
34def run_and_tee_output(args):
35    """Runs the test executable passing-thru its output to stdout (in a
36    terminal-colors-friendly way).  Waits for the executable to exit.
37
38    Returns:
39        The full executable output as an UTF-8 string.
40    """
41    # Capture stdout (where test results are written), but inherit stderr. This
42    # way errors related to invalid arguments are printed normally.
43    proc = subprocess.Popen(args, stdout=subprocess.PIPE)
44    captured_output = b''
45    while proc.poll() is None:
46        buf = proc.stdout.read()
47        # Write captured output directly, so escape sequences are preserved.
48        sys.stdout.buffer.write(buf)
49        captured_output += buf
50
51    captured_output = _ANSI_ESCAPE_8BIT_REGEX.sub(
52        '', captured_output.decode('utf-8'))
53
54    return captured_output
55