diff options
Diffstat (limited to 'tools/u_boot_pylib')
-rwxr-xr-x | tools/u_boot_pylib/__main__.py | 2 | ||||
-rw-r--r-- | tools/u_boot_pylib/command.py | 5 | ||||
-rw-r--r-- | tools/u_boot_pylib/gitutil.py | 255 | ||||
-rw-r--r-- | tools/u_boot_pylib/terminal.py | 105 | ||||
-rw-r--r-- | tools/u_boot_pylib/test_util.py | 29 | ||||
-rw-r--r-- | tools/u_boot_pylib/tout.py | 27 |
6 files changed, 320 insertions, 103 deletions
diff --git a/tools/u_boot_pylib/__main__.py b/tools/u_boot_pylib/__main__.py index c0762bca733..d86b9d7dce0 100755 --- a/tools/u_boot_pylib/__main__.py +++ b/tools/u_boot_pylib/__main__.py @@ -16,7 +16,7 @@ if __name__ == "__main__": from u_boot_pylib import test_util result = test_util.run_test_suites( - 'u_boot_pylib', False, False, False, None, None, None, + 'u_boot_pylib', False, False, False, False, None, None, None, ['terminal']) sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tools/u_boot_pylib/command.py b/tools/u_boot_pylib/command.py index 0e247355ef6..cb7ebf49ce5 100644 --- a/tools/u_boot_pylib/command.py +++ b/tools/u_boot_pylib/command.py @@ -203,7 +203,7 @@ def run_one(*cmd, **kwargs): return run_pipe([cmd], **kwargs) -def run_list(cmd): +def run_list(cmd, **kwargs): """Run a command and return its output Args: @@ -211,8 +211,9 @@ def run_list(cmd): Returns: str: output of command + **kwargs (dict of args): Extra arguments to pass in """ - return run_pipe([cmd], capture=True).stdout + return run_pipe([cmd], capture=True, **kwargs).stdout def stop_all(): diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py index 0376bece3e6..34b4dbb4839 100644 --- a/tools/u_boot_pylib/gitutil.py +++ b/tools/u_boot_pylib/gitutil.py @@ -2,10 +2,11 @@ # Copyright (c) 2011 The Chromium OS Authors. # +"""Basic utilities for running the git command-line tool from Python""" + import os import sys -from patman import settings from u_boot_pylib import command from u_boot_pylib import terminal @@ -14,7 +15,7 @@ USE_NO_DECORATE = True def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False, - count=None): + count=None, decorate=False): """Create a command to perform a 'git log' Args: @@ -23,6 +24,8 @@ def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False, oneline (bool): True to use --oneline, else False reverse (bool): True to reverse the log (--reverse) count (int or None): Number of commits to list, or None for no limit + decorate (bool): True to use --decorate + Return: List containing command and arguments to run """ @@ -32,8 +35,10 @@ def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False, cmd += ['--no-pager', 'log', '--no-color'] if oneline: cmd.append('--oneline') - if USE_NO_DECORATE: + if USE_NO_DECORATE and not decorate: cmd.append('--no-decorate') + if decorate: + cmd.append('--decorate') if reverse: cmd.append('--reverse') if count is not None: @@ -48,7 +53,7 @@ def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False, return cmd -def count_commits_to_branch(branch): +def count_commits_to_branch(branch, git_dir=None, end=None): """Returns number of commits between HEAD and the tracking branch. This looks back to the tracking branch and works out the number of commits @@ -56,16 +61,22 @@ def count_commits_to_branch(branch): Args: branch (str or None): Branch to count from (None for current branch) + git_dir (str): Path to git repository (None to use default) + end (str): End commit to stop before Return: Number of patches that exist on top of the branch """ - if branch: - us, _ = get_upstream('.git', branch) + if end: + rev_range = f'{end}..{branch}' + elif branch: + us, msg = get_upstream(git_dir or '.git', branch) + if not us: + raise ValueError(msg) rev_range = f'{us}..{branch}' else: rev_range = '@{upstream}..' - cmd = log_cmd(rev_range, oneline=True) + cmd = log_cmd(rev_range, git_dir=git_dir, oneline=True) result = command.run_one(*cmd, capture=True, capture_stderr=True, oneline=True, raise_on_error=False) if result.return_code: @@ -85,9 +96,11 @@ def name_revision(commit_hash): Name of revision, if any, else None """ stdout = command.output_one_line('git', 'name-rev', commit_hash) + if not stdout: + return None # We expect a commit, a space, then a revision name - name = stdout.split(' ')[1].strip() + name = stdout.split()[1].strip() return name @@ -107,18 +120,21 @@ def guess_upstream(git_dir, branch): Name of upstream branch (e.g. 'upstream/master') or None if none Warning/error message, or None if none """ - cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100) + cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100, + decorate=True) result = command.run_one(*cmd, capture=True, capture_stderr=True, raise_on_error=False) if result.return_code: return None, f"Branch '{branch}' not found" for line in result.stdout.splitlines()[1:]: - commit_hash = line.split(' ')[0] - name = name_revision(commit_hash) - if '~' not in name and '^' not in name: - if name.startswith('remotes/'): - name = name[8:] - return name, f"Guessing upstream as '{name}'" + parts = line.split(maxsplit=1) + if len(parts) >= 2 and parts[1].startswith('('): + commit_hash = parts[0] + name = name_revision(commit_hash) + if '~' not in name and '^' not in name: + if name.startswith('remotes/'): + name = name[8:] + return name, f"Guessing upstream as '{name}'" return None, f"Cannot find a suitable upstream for branch '{branch}'" @@ -322,7 +338,8 @@ def prune_worktrees(git_dir): raise OSError(f'git worktree prune: {result.stderr}') -def create_patches(branch, start, count, ignore_binary, series, signoff=True): +def create_patches(branch, start, count, ignore_binary, series, signoff=True, + git_dir=None, cwd=None): """Create a series of patches from the top of the current branch. The patch files are written to the current directory using @@ -335,11 +352,16 @@ def create_patches(branch, start, count, ignore_binary, series, signoff=True): ignore_binary (bool): Don't generate patches for binary files series (Series): Series object for this series (set of patches) signoff (bool): True to add signoff lines automatically + git_dir (str): Path to git repository (None to use default) + cwd (str): Path to use for git operations Return: Filename of cover letter (None if none) List of filenames of patch files """ - cmd = ['git', 'format-patch', '-M'] + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['format-patch', '-M'] if signoff: cmd.append('--signoff') if ignore_binary: @@ -352,7 +374,7 @@ def create_patches(branch, start, count, ignore_binary, series, signoff=True): brname = branch or 'HEAD' cmd += [f'{brname}~{start + count}..{brname}~{start}'] - stdout = command.run_list(cmd) + stdout = command.run_list(cmd, cwd=cwd) files = stdout.splitlines() # We have an extra file if there is a cover letter @@ -361,7 +383,7 @@ def create_patches(branch, start, count, ignore_binary, series, signoff=True): return None, files -def build_email_list(in_list, tag=None, alias=None, warn_on_error=True): +def build_email_list(in_list, alias, tag=None, warn_on_error=True): """Build a list of email addresses based on an input list. Takes a list of email addresses and aliases, and turns this into a list @@ -373,10 +395,10 @@ def build_email_list(in_list, tag=None, alias=None, warn_on_error=True): Args: in_list (list of str): List of aliases/email addresses - tag (str): Text to put before each address alias (dict): Alias dictionary: key: alias value: list of aliases or email addresses + tag (str): Text to put before each address warn_on_error (bool): True to raise an error when an alias fails to match, False to just print a message. @@ -389,15 +411,14 @@ def build_email_list(in_list, tag=None, alias=None, warn_on_error=True): >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>'] >>> alias['boys'] = ['fred', ' john'] >>> alias['all'] = ['fred ', 'john', ' mary '] - >>> build_email_list(['john', 'mary'], None, alias) + >>> build_email_list(['john', 'mary'], alias, None) ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>'] - >>> build_email_list(['john', 'mary'], '--to', alias) + >>> build_email_list(['john', 'mary'], alias, '--to') ['--to "j.bloggs@napier.co.nz"', \ '--to "Mary Poppins <m.poppins@cloud.net>"'] - >>> build_email_list(['john', 'mary'], 'Cc', alias) + >>> build_email_list(['john', 'mary'], alias, 'Cc') ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>'] """ - quote = '"' if tag and tag[0] == '-' else '' raw = [] for item in in_list: raw += lookup_email(item, alias, warn_on_error=warn_on_error) @@ -406,7 +427,7 @@ def build_email_list(in_list, tag=None, alias=None, warn_on_error=True): if item not in result: result.append(item) if tag: - return [f'{tag} {quote}{email}{quote}' for email in result] + return [x for email in result for x in (tag, email)] return result @@ -437,8 +458,8 @@ def check_suppress_cc_config(): def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname, - self_only=False, alias=None, in_reply_to=None, thread=False, - smtp_server=None): + alias, self_only=False, in_reply_to=None, thread=False, + smtp_server=None, cwd=None): """Email a patch series. Args: @@ -449,15 +470,16 @@ def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname, warn_on_error (bool): True to print a warning when an alias fails to match, False to ignore it. cc_fname (str): Filename of Cc file for per-commit Cc - self_only (bool): True to just email to yourself as a test - alias (dict or None): Alias dictionary: (None to use settings default) + alias (dict): Alias dictionary: key: alias value: list of aliases or email addresses + self_only (bool): True to just email to yourself as a test in_reply_to (str or None): If set we'll pass this to git as --in-reply-to - should be a message ID that this is in reply to. thread (bool): True to add --thread to git send-email (make all patches reply to cover-letter or first patch in series) smtp_server (str or None): SMTP server to use to send patches + cwd (str): Path to use for patch files (None to use current dir) Returns: Git command that was/would be run @@ -498,11 +520,10 @@ send --cc-cmd cc-fname" cover p1 p2' # Restore argv[0] since we clobbered it. >>> sys.argv[0] = _old_argv0 """ - to = build_email_list(series.get('to'), '--to', alias, warn_on_error) + to = build_email_list(series.get('to'), alias, '--to', warn_on_error) if not to: - git_config_to = command.output('git', 'config', 'sendemail.to', - raise_on_error=False) - if not git_config_to: + if not command.output('git', 'config', 'sendemail.to', + raise_on_error=False): print("No recipient.\n" "Please add something like this to a commit\n" "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n" @@ -510,10 +531,10 @@ send --cc-cmd cc-fname" cover p1 p2' "git config sendemail.to u-boot@lists.denx.de") return None cc = build_email_list(list(set(series.get('cc')) - set(series.get('to'))), - '--cc', alias, warn_on_error) + alias, '--cc', warn_on_error) if self_only: - to = build_email_list([os.getenv('USER')], '--to', - alias, warn_on_error) + to = build_email_list([os.getenv('USER')], '--to', alias, + warn_on_error) cc = [] cmd = ['git', 'send-email', '--annotate'] if smtp_server: @@ -525,24 +546,24 @@ send --cc-cmd cc-fname" cover p1 p2' cmd += to cmd += cc - cmd += ['--cc-cmd', f'"{sys.argv[0]} send --cc-cmd {cc_fname}"'] + cmd += ['--cc-cmd', f'{sys.argv[0]} send --cc-cmd {cc_fname}'] if cover_fname: cmd.append(cover_fname) cmd += args - cmdstr = ' '.join(cmd) if not dry_run: - os.system(cmdstr) - return cmdstr + command.run(*cmd, capture=False, capture_stderr=False, cwd=cwd) + return' '.join([f'"{x}"' if ' ' in x and '"' not in x else x + for x in cmd]) -def lookup_email(lookup_name, alias=None, warn_on_error=True, level=0): +def lookup_email(lookup_name, alias, warn_on_error=True, level=0): """If an email address is an alias, look it up and return the full name TODO: Why not just use git's own alias feature? Args: lookup_name (str): Alias or email address to look up - alias (dict or None): Alias dictionary: (None to use settings default) + alias (dict): Alias dictionary key: alias value: list of aliases or email addresses warn_on_error (bool): True to print a warning when an alias fails to @@ -589,8 +610,6 @@ def lookup_email(lookup_name, alias=None, warn_on_error=True, level=0): Recursive email alias at 'mary' ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] """ - if not alias: - alias = settings.alias lookup_name = lookup_name.strip() if '@' in lookup_name: # Perhaps a real email address return [lookup_name] @@ -625,7 +644,7 @@ def get_top_level(): """Return name of top-level directory for this git repo. Returns: - str: Full path to git top-level directory + str: Full path to git top-level directory, or None if not found This test makes sure that we are running tests in the right subdir @@ -633,7 +652,12 @@ def get_top_level(): os.path.join(get_top_level(), 'tools', 'patman') True """ - return command.output_one_line('git', 'rev-parse', '--show-toplevel') + result = command.run_one( + 'git', 'rev-parse', '--show-toplevel', oneline=True, capture=True, + capture_stderr=True, raise_on_error=False) + if result.return_code: + return None + return result.stdout.strip() def get_alias_file(): @@ -651,7 +675,7 @@ def get_alias_file(): if os.path.isabs(fname): return fname - return os.path.join(get_top_level(), fname) + return os.path.join(get_top_level() or '', fname) def get_default_user_name(): @@ -693,25 +717,26 @@ def setup(): # Check for a git alias file also global USE_NO_DECORATE - alias_fname = get_alias_file() - if alias_fname: - settings.ReadGitAliases(alias_fname) cmd = log_cmd(None, count=0) USE_NO_DECORATE = (command.run_one(*cmd, raise_on_error=False) .return_code == 0) -def get_hash(spec): +def get_hash(spec, git_dir=None): """Get the hash of a commit Args: spec (str): Git commit to show, e.g. 'my-branch~12' + git_dir (str): Path to git repository (None to use default) Returns: str: Hash of commit """ - return command.output_one_line('git', 'show', '-s', '--pretty=format:%H', - spec) + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['show', '-s', '--pretty=format:%H', spec] + return command.output_one_line(*cmd) def get_head(): @@ -723,18 +748,138 @@ def get_head(): return get_hash('HEAD') -def get_branch(): +def get_branch(git_dir=None): """Get the branch we are currently on Return: str: branch name, or None if none + git_dir (str): Path to git repository (None to use default) """ - out = command.output_one_line('git', 'rev-parse', '--abbrev-ref', 'HEAD') + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['rev-parse', '--abbrev-ref', 'HEAD'] + out = command.output_one_line(*cmd, raise_on_error=False) if out == 'HEAD': return None return out +def check_dirty(git_dir=None, work_tree=None): + """Check if the tree is dirty + + Args: + git_dir (str): Path to git repository (None to use default) + work_tree (str): Git worktree to use, or None if none + + Return: + str: List of dirty filenames and state + """ + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + if work_tree: + cmd += ['--work-tree', work_tree] + cmd += ['status', '--porcelain', '--untracked-files=no'] + return command.output(*cmd).splitlines() + + +def check_branch(name, git_dir=None): + """Check if a branch exists + + Args: + name (str): Name of the branch to check + git_dir (str): Path to git repository (None to use default) + """ + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['branch', '--list', name] + + # This produces ' <name>' or '* <name>' + out = command.output(*cmd).rstrip() + return out[2:] == name + + +def rename_branch(old_name, name, git_dir=None): + """Check if a branch exists + + Args: + old_name (str): Name of the branch to rename + name (str): New name for the branch + git_dir (str): Path to git repository (None to use default) + + Return: + str: Output from command + """ + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['branch', '--move', old_name, name] + + # This produces ' <name>' or '* <name>' + return command.output(*cmd).rstrip() + + +def get_commit_message(commit, git_dir=None): + """Gets the commit message for a commit + + Args: + commit (str): commit to check + git_dir (str): Path to git repository (None to use default) + + Return: + list of str: Lines from the commit message + """ + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['show', '--quiet', commit] + + out = command.output(*cmd) + # the header is followed by a blank line + lines = out.splitlines() + empty = lines.index('') + msg = lines[empty + 1:] + unindented = [line[4:] for line in msg] + + return unindented + + +def show_commit(commit, msg=True, diffstat=False, patch=False, colour=True, + git_dir=None): + """Runs 'git show' and returns the output + + Args: + commit (str): commit to check + msg (bool): Show the commit message + diffstat (bool): True to include the diffstat + patch (bool): True to include the patch + colour (bool): True to force use of colour + git_dir (str): Path to git repository (None to use default) + + Return: + list of str: Lines from the commit message + """ + cmd = ['git'] + if git_dir: + cmd += ['--git-dir', git_dir] + cmd += ['show'] + if colour: + cmd.append('--color') + if not msg: + cmd.append('--oneline') + if diffstat: + cmd.append('--stat') + else: + cmd.append('--quiet') + if patch: + cmd.append('--patch') + cmd.append(commit) + + return command.output(*cmd) + + if __name__ == "__main__": import doctest diff --git a/tools/u_boot_pylib/terminal.py b/tools/u_boot_pylib/terminal.py index 2cd5a54ab52..69c183e85e5 100644 --- a/tools/u_boot_pylib/terminal.py +++ b/tools/u_boot_pylib/terminal.py @@ -7,9 +7,12 @@ This module handles terminal interaction including ANSI color codes. """ +from contextlib import contextmanager +from io import StringIO import os import re import shutil +import subprocess import sys # Selection of when we want our output to be colored @@ -26,6 +29,13 @@ last_print_len = None # stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python ansi_escape = re.compile(r'\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') +# True if we are capturing console output +CAPTURING = False + +# Set this to False to disable output-capturing globally +USE_CAPTURE = True + + class PrintLine: """A line of text output @@ -130,7 +140,8 @@ def trim_ascii_len(text, size): return out -def tprint(text='', newline=True, colour=None, limit_to_line=False, bright=True): +def tprint(text='', newline=True, colour=None, limit_to_line=False, + bright=True, back=None, col=None): """Handle a line of output to the terminal. In test mode this is recorded in a list. Otherwise it is output to the @@ -146,9 +157,10 @@ def tprint(text='', newline=True, colour=None, limit_to_line=False, bright=True) if print_test_mode: print_test_list.append(PrintLine(text, colour, newline, bright)) else: - if colour: - col = Color() - text = col.build(colour, text, bright=bright) + if colour is not None: + if not col: + col = Color() + text = col.build(colour, text, bright=bright, back=back) if newline: print(text) last_print_len = None @@ -200,14 +212,23 @@ def echo_print_test_lines(): if line.newline: print() +def have_terminal(): + """Check if we have an interactive terminal or not + + Returns: + bool: true if an interactive terminal is attached + """ + return os.isatty(sys.stdout.fileno()) + -class Color(object): +class Color(): """Conditionally wraps text in ANSI color escape sequences.""" BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) BOLD = -1 - BRIGHT_START = '\033[1;%dm' - NORMAL_START = '\033[22;%dm' + BRIGHT_START = '\033[1;%d%sm' + NORMAL_START = '\033[22;%d%sm' BOLD_START = '\033[1m' + BACK_EXTRA = ';%d' RESET = '\033[0m' def __init__(self, colored=COLOR_IF_TERMINAL): @@ -224,7 +245,14 @@ class Color(object): except: self._enabled = False - def start(self, color, bright=True): + def enabled(self): + """Check if colour is enabled + + Return: True if enabled, else False + """ + return self._enabled + + def start(self, color, bright=True, back=None): """Returns a start color code. Args: @@ -235,8 +263,11 @@ class Color(object): color, otherwise returns empty string """ if self._enabled: + if color == self.BOLD: + return self.BOLD_START base = self.BRIGHT_START if bright else self.NORMAL_START - return base % (color + 30) + extra = self.BACK_EXTRA % (back + 40) if back else '' + return base % (color + 30, extra) return '' def stop(self): @@ -250,7 +281,7 @@ class Color(object): return self.RESET return '' - def build(self, color, text, bright=True): + def build(self, color, text, bright=True, back=None): """Returns text with conditionally added color escape sequences. Keyword arguments: @@ -265,9 +296,51 @@ class Color(object): """ if not self._enabled: return text - if color == self.BOLD: - start = self.BOLD_START - else: - base = self.BRIGHT_START if bright else self.NORMAL_START - start = base % (color + 30) - return start + text + self.RESET + return self.start(color, bright, back) + text + self.RESET + + +# Use this to suppress stdout/stderr output: +# with terminal.capture() as (stdout, stderr) +# ...do something... +@contextmanager +def capture(): + global CAPTURING + + capture_out, capture_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + CAPTURING = True + sys.stdout, sys.stderr = capture_out, capture_err + yield capture_out, capture_err + finally: + sys.stdout, sys.stderr = old_out, old_err + CAPTURING = False + if not USE_CAPTURE: + sys.stdout.write(capture_out.getvalue()) + sys.stderr.write(capture_err.getvalue()) + + +@contextmanager +def pager(): + """Simple pager for outputting lots of text + + Usage: + with terminal.pager(): + print(...) + """ + proc = None + old_stdout = None + try: + less = os.getenv('PAGER') + if not CAPTURING and less != 'none' and have_terminal(): + if not less: + less = 'less -R --quit-if-one-screen' + proc = subprocess.Popen(less, stdin=subprocess.PIPE, text=True, + shell=True) + old_stdout = sys.stdout + sys.stdout = proc.stdin + yield + finally: + if proc: + sys.stdout = old_stdout + proc.communicate() diff --git a/tools/u_boot_pylib/test_util.py b/tools/u_boot_pylib/test_util.py index 637403f8715..d258a1935c9 100644 --- a/tools/u_boot_pylib/test_util.py +++ b/tools/u_boot_pylib/test_util.py @@ -3,7 +3,6 @@ # Copyright (c) 2016 Google, Inc # -from contextlib import contextmanager import doctest import glob import multiprocessing @@ -13,8 +12,7 @@ import sys import unittest from u_boot_pylib import command - -from io import StringIO +from u_boot_pylib import terminal use_concurrent = True try: @@ -113,20 +111,6 @@ def run_test_coverage(prog, filter_fname, exclude_list, build_dir, raise ValueError('Test coverage failure') -# Use this to suppress stdout/stderr output: -# with capture_sys_output() as (stdout, stderr) -# ...do something... -@contextmanager -def capture_sys_output(): - capture_out, capture_err = StringIO(), StringIO() - old_out, old_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = capture_out, capture_err - yield capture_out, capture_err - finally: - sys.stdout, sys.stderr = old_out, old_err - - class FullTextTestResult(unittest.TextTestResult): """A test result class that can print extended text results to a stream @@ -172,8 +156,8 @@ class FullTextTestResult(unittest.TextTestResult): super().addSkip(test, reason) -def run_test_suites(toolname, debug, verbosity, test_preserve_dirs, processes, - test_name, toolpath, class_and_module_list): +def run_test_suites(toolname, debug, verbosity, no_capture, test_preserve_dirs, + processes, test_name, toolpath, class_and_module_list): """Run a series of test suites and collect the results Args: @@ -196,6 +180,9 @@ def run_test_suites(toolname, debug, verbosity, test_preserve_dirs, processes, sys.argv.append('-D') if verbosity: sys.argv.append('-v%d' % verbosity) + if no_capture: + sys.argv.append('-N') + terminal.USE_CAPTURE = False if toolpath: for path in toolpath: sys.argv += ['--toolpath', path] @@ -208,7 +195,7 @@ def run_test_suites(toolname, debug, verbosity, test_preserve_dirs, processes, resultclass=FullTextTestResult, ) - if use_concurrent and processes != 1: + if use_concurrent and processes != 1 and not test_name: suite = ConcurrentTestSuite(suite, fork_for_tests(processes or multiprocessing.cpu_count())) @@ -224,7 +211,7 @@ def run_test_suites(toolname, debug, verbosity, test_preserve_dirs, processes, setup_test_args = getattr(module, 'setup_test_args') setup_test_args(preserve_indir=test_preserve_dirs, preserve_outdirs=test_preserve_dirs and test_name is not None, - toolpath=toolpath, verbosity=verbosity) + toolpath=toolpath, verbosity=verbosity, no_capture=no_capture) if test_name: # Since Python v3.5 If an ImportError or AttributeError occurs # while traversing a name then a synthetic test that raises that diff --git a/tools/u_boot_pylib/tout.py b/tools/u_boot_pylib/tout.py index 6bd2806f88f..ca72108d6bc 100644 --- a/tools/u_boot_pylib/tout.py +++ b/tools/u_boot_pylib/tout.py @@ -9,7 +9,7 @@ import sys from u_boot_pylib import terminal # Output verbosity levels that we support -ERROR, WARNING, NOTICE, INFO, DETAIL, DEBUG = range(6) +FATAL, ERROR, WARNING, NOTICE, INFO, DETAIL, DEBUG = range(7) in_progress = False @@ -42,12 +42,12 @@ def user_is_present(): Returns: True if it thinks the user is there, and False otherwise """ - return stdout_is_tty and verbose > 0 + return stdout_is_tty and verbose > ERROR def clear_progress(): """Clear any active progress message on the terminal.""" global in_progress - if verbose > 0 and stdout_is_tty and in_progress: + if verbose > ERROR and stdout_is_tty and in_progress: _stdout.write('\r%s\r' % (" " * len (_progress))) _stdout.flush() in_progress = False @@ -60,7 +60,7 @@ def progress(msg, warning=False, trailer='...'): warning: True if this is a warning.""" global in_progress clear_progress() - if verbose > 0: + if verbose > ERROR: _progress = msg + trailer if stdout_is_tty: col = _color.YELLOW if warning else _color.GREEN @@ -87,6 +87,8 @@ def _output(level, msg, color=None): print(msg, file=sys.stderr) else: print(msg) + if level == FATAL: + sys.exit(1) def do_output(level, msg): """Output a message to the terminal. @@ -98,6 +100,14 @@ def do_output(level, msg): """ _output(level, msg) +def fatal(msg): + """Display an error message and exit + + Args: + msg; Message to display. + """ + _output(FATAL, msg, _color.RED) + def error(msg): """Display an error message @@ -153,20 +163,21 @@ def user_output(msg): Args: msg; Message to display. """ - _output(0, msg) + _output(ERROR, msg) -def init(_verbose=WARNING, stdout=sys.stdout): +def init(_verbose=WARNING, stdout=sys.stdout, allow_colour=True): """Initialize a new output object. Args: - verbose: Verbosity level (0-4). + verbose: Verbosity level (0-6). stdout: File to use for stdout. """ global verbose, _progress, _color, _stdout, stdout_is_tty verbose = _verbose _progress = '' # Our last progress message - _color = terminal.Color() + _color = terminal.Color(terminal.COLOR_IF_TERMINAL if allow_colour + else terminal.COLOR_NEVER) _stdout = stdout # TODO(sjg): Move this into Chromite libraries when we have them |