Patrick Williams | ac13d5f | 2023-11-24 18:59:46 -0600 | [diff] [blame] | 1 | # ex:ts=4:sw=4:sts=4:et |
| 2 | # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| 3 | # |
| 4 | # utils: common methods used by the patchtest framework |
| 5 | # |
| 6 | # Copyright (C) 2016 Intel Corporation |
| 7 | # |
| 8 | # SPDX-License-Identifier: GPL-2.0-only |
| 9 | # |
| 10 | |
| 11 | import os |
| 12 | import subprocess |
| 13 | import logging |
| 14 | import re |
| 15 | import mailbox |
| 16 | |
| 17 | class CmdException(Exception): |
| 18 | """ Simple exception class where its attributes are the ones passed when instantiated """ |
| 19 | def __init__(self, cmd): |
| 20 | self._cmd = cmd |
| 21 | def __getattr__(self, name): |
| 22 | value = None |
| 23 | if self._cmd.has_key(name): |
| 24 | value = self._cmd[name] |
| 25 | return value |
| 26 | |
| 27 | def exec_cmd(cmd, cwd, ignore_error=False, input=None, strip=True, updateenv={}): |
| 28 | """ |
| 29 | Input: |
| 30 | |
| 31 | cmd: dict containing the following keys: |
| 32 | |
| 33 | cmd : the command itself as an array of strings |
| 34 | ignore_error: if False, no exception is raised |
| 35 | strip: indicates if strip is done on the output (stdout and stderr) |
| 36 | input: input data to the command (stdin) |
| 37 | updateenv: environment variables to be appended to the current |
| 38 | process environment variables |
| 39 | |
| 40 | NOTE: keys 'ignore_error' and 'input' are optional; if not included, |
| 41 | the defaults are the ones specify in the arguments |
| 42 | cwd: directory where commands are executed |
| 43 | ignore_error: raise CmdException if command fails to execute and |
| 44 | this value is False |
| 45 | input: input data (stdin) for the command |
| 46 | |
| 47 | Output: dict containing the following keys: |
| 48 | |
| 49 | cmd: the same as input |
| 50 | ignore_error: the same as input |
| 51 | strip: the same as input |
| 52 | input: the same as input |
| 53 | stdout: Standard output after command's execution |
| 54 | stderr: Standard error after command's execution |
| 55 | returncode: Return code after command's execution |
| 56 | |
| 57 | """ |
| 58 | cmddefaults = { |
| 59 | 'cmd':'', |
| 60 | 'ignore_error':ignore_error, |
| 61 | 'strip':strip, |
| 62 | 'input':input, |
| 63 | 'updateenv':updateenv, |
| 64 | } |
| 65 | |
| 66 | # update input values if necessary |
| 67 | cmddefaults.update(cmd) |
| 68 | |
| 69 | _cmd = cmddefaults |
| 70 | |
| 71 | if not _cmd['cmd']: |
| 72 | raise CmdException({'cmd':None, 'stderr':'no command given'}) |
| 73 | |
| 74 | # update the environment |
| 75 | env = os.environ |
| 76 | env.update(_cmd['updateenv']) |
| 77 | |
| 78 | _command = [e for e in _cmd['cmd']] |
| 79 | p = subprocess.Popen(_command, |
| 80 | stdin=subprocess.PIPE, |
| 81 | stdout=subprocess.PIPE, |
| 82 | stderr=subprocess.PIPE, |
| 83 | universal_newlines=True, |
| 84 | cwd=cwd, |
| 85 | env=env) |
| 86 | |
| 87 | # execute the command and strip output |
| 88 | (_stdout, _stderr) = p.communicate(_cmd['input']) |
| 89 | if _cmd['strip']: |
| 90 | _stdout, _stderr = map(str.strip, [_stdout, _stderr]) |
| 91 | |
| 92 | # generate the result |
| 93 | result = _cmd |
| 94 | result.update({'cmd':_command,'stdout':_stdout,'stderr':_stderr,'returncode':p.returncode}) |
| 95 | |
| 96 | # launch exception if necessary |
| 97 | if not _cmd['ignore_error'] and p.returncode: |
| 98 | raise CmdException(result) |
| 99 | |
| 100 | return result |
| 101 | |
| 102 | def exec_cmds(cmds, cwd): |
| 103 | """ Executes commands |
| 104 | |
| 105 | Input: |
| 106 | cmds: Array of commands |
| 107 | cwd: directory where commands are executed |
| 108 | |
| 109 | Output: Array of output commands |
| 110 | """ |
| 111 | results = [] |
| 112 | _cmds = cmds |
| 113 | |
| 114 | for cmd in _cmds: |
| 115 | result = exec_cmd(cmd, cwd) |
| 116 | results.append(result) |
| 117 | |
| 118 | return results |
| 119 | |
| 120 | def logger_create(name): |
| 121 | logger = logging.getLogger(name) |
| 122 | loggerhandler = logging.StreamHandler() |
| 123 | loggerhandler.setFormatter(logging.Formatter("%(message)s")) |
| 124 | logger.addHandler(loggerhandler) |
| 125 | logger.setLevel(logging.INFO) |
| 126 | return logger |
| 127 | |
| 128 | def get_subject_prefix(path): |
| 129 | prefix = "" |
| 130 | mbox = mailbox.mbox(path) |
| 131 | |
| 132 | if len(mbox): |
| 133 | subject = mbox[0]['subject'] |
| 134 | if subject: |
| 135 | pattern = re.compile("(\[.*\])", re.DOTALL) |
| 136 | match = pattern.search(subject) |
| 137 | if match: |
| 138 | prefix = match.group(1) |
| 139 | |
| 140 | return prefix |
| 141 | |
| 142 | def valid_branch(branch): |
| 143 | """ Check if branch is valid name """ |
| 144 | lbranch = branch.lower() |
| 145 | |
| 146 | invalid = lbranch.startswith('patch') or \ |
| 147 | lbranch.startswith('rfc') or \ |
| 148 | lbranch.startswith('resend') or \ |
| 149 | re.search('^v\d+', lbranch) or \ |
| 150 | re.search('^\d+/\d+', lbranch) |
| 151 | |
| 152 | return not invalid |
| 153 | |
| 154 | def get_branch(path): |
| 155 | """ Get the branch name from mbox """ |
| 156 | fullprefix = get_subject_prefix(path) |
| 157 | branch, branches, valid_branches = None, [], [] |
| 158 | |
| 159 | if fullprefix: |
| 160 | prefix = fullprefix.strip('[]') |
| 161 | branches = [ b.strip() for b in prefix.split(',')] |
| 162 | valid_branches = [b for b in branches if valid_branch(b)] |
| 163 | |
| 164 | if len(valid_branches): |
| 165 | branch = valid_branches[0] |
| 166 | |
| 167 | return branch |
| 168 | |