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