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 subprocess 8import re 9import sys 10 11# Regex for matching 7-bit and 8-bit C1 ANSI sequences. 12# Credit: https://stackoverflow.com/a/14693789/4692014 13_ANSI_ESCAPE_8BIT_REGEX = re.compile( 14 r""" 15 (?: # either 7-bit C1, two bytes, ESC Fe (omitting CSI) 16 \x1B 17 [@-Z\\-_] 18 | # or a single 8-bit byte Fe (omitting CSI) 19 [\x80-\x9A\x9C-\x9F] 20 | # or CSI + control codes 21 (?: # 7-bit CSI, ESC [ 22 \x1B\[ 23 | # 8-bit CSI, 9B 24 \x9B 25 ) 26 [0-?]* # Parameter bytes 27 [ -/]* # Intermediate bytes 28 [@-~] # Final byte 29 ) 30""", re.VERBOSE) 31 32 33def run_and_tee_output(args): 34 """Runs the test executable passing-thru its output to stdout (in a 35 terminal-colors-friendly way). Waits for the executable to exit. 36 37 Returns: 38 The full executable output as an UTF-8 string. 39 """ 40 # Capture stdout (where test results are written), but inherit stderr. This 41 # way errors related to invalid arguments are printed normally. 42 proc = subprocess.Popen(args, stdout=subprocess.PIPE) 43 captured_output = b'' 44 while proc.poll() is None: 45 buf = proc.stdout.read() 46 # Write captured output directly, so escape sequences are preserved. 47 sys.stdout.buffer.write(buf) 48 captured_output += buf 49 50 captured_output = _ANSI_ESCAPE_8BIT_REGEX.sub( 51 '', captured_output.decode('utf-8')) 52 53 return captured_output 54