# Copyright 2020 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Creates and manages test runner log file objects. Provides a context manager object for use in a with statement and a module level FileStreamFor function for use by clients. """ import collections import multiprocessing import os from symbolizer import RunSymbolizer SYMBOLIZED_SUFFIX = '.symbolized' _RunnerLogEntry = collections.namedtuple( '_RunnerLogEntry', ['name', 'log_file', 'path', 'symbolize']) # Module singleton variable. _instance = None class RunnerLogManager(object): """ Runner logs object for use in a with statement.""" def __init__(self, log_dir, build_ids_files): global _instance if _instance: raise Exception('Only one RunnerLogManager can be instantiated') self._log_dir = log_dir self._build_ids_files = build_ids_files self._runner_logs = [] if self._log_dir and not os.path.isdir(self._log_dir): os.makedirs(self._log_dir) _instance = self def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pool = multiprocessing.Pool(4) for log_entry in self._runner_logs: pool.apply_async(_FinalizeLog, (log_entry, self._build_ids_files)) pool.close() pool.join() _instance = None def _FileStreamFor(self, name, symbolize): if any(elem.name == name for elem in self._runner_logs): raise Exception('RunnerLogManager can only open "%s" once' % name) path = os.path.join(self._log_dir, name) if self._log_dir else os.devnull log_file = open(path, 'w') self._runner_logs.append(_RunnerLogEntry(name, log_file, path, symbolize)) return log_file def _FinalizeLog(log_entry, build_ids_files): log_entry.log_file.close() if log_entry.symbolize: input_file = open(log_entry.path, 'r') output_file = open(log_entry.path + SYMBOLIZED_SUFFIX, 'w') proc = RunSymbolizer(input_file, output_file, build_ids_files) proc.wait() output_file.close() input_file.close() def IsEnabled(): """Returns True if the RunnerLogManager has been created, or False if not.""" return _instance is not None and _instance._log_dir is not None def FileStreamFor(name, symbolize=False): """Opens a test runner file stream in the test runner log directory. If no test runner log directory is specified, output is discarded. name: log file name symbolize: if True, make a symbolized copy of the log after closing it. Returns an opened log file object.""" return _instance._FileStreamFor(name, symbolize) if IsEnabled() else open( os.devnull, 'w')