summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rwxr-xr-xutil/build_with_clang.py42
-rw-r--r--util/chargen25
-rwxr-xr-xutil/config_option_check.py693
-rwxr-xr-xutil/ec3po/console.py2231
-rwxr-xr-xutil/ec3po/console_unittest.py2954
-rw-r--r--util/ec3po/interpreter.py818
-rwxr-xr-xutil/ec3po/interpreter_unittest.py728
-rw-r--r--util/ec3po/threadproc_shim.py31
-rwxr-xr-xutil/ec_openocd.py24
-rwxr-xr-xutil/flash_jlink.py144
-rwxr-xr-xutil/fptool.py17
-rwxr-xr-xutil/inject-keys.py149
-rwxr-xr-xutil/kconfig_check.py230
-rw-r--r--util/kconfiglib.py1581
-rw-r--r--util/run_ects.py144
-rw-r--r--util/test_kconfig_check.py181
-rwxr-xr-xutil/uart_stress_tester.py1015
-rwxr-xr-xutil/unpack_ftb.py124
-rwxr-xr-xutil/update_release_branch.py251
19 files changed, 6052 insertions, 5330 deletions
diff --git a/util/build_with_clang.py b/util/build_with_clang.py
index a38ade2cb8..98da942152 100755
--- a/util/build_with_clang.py
+++ b/util/build_with_clang.py
@@ -12,15 +12,14 @@ import multiprocessing
import os
import subprocess
import sys
-
from concurrent.futures import ThreadPoolExecutor
# Add to this list as compilation errors are fixed for boards.
BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
- 'dartmonkey',
- 'bloonchipper',
- 'nucleo-f412zg',
- 'nucleo-h743zi',
+ "dartmonkey",
+ "bloonchipper",
+ "nucleo-f412zg",
+ "nucleo-h743zi",
]
@@ -29,35 +28,29 @@ def build(board_name: str) -> None:
logging.debug('Building board: "%s"', board_name)
cmd = [
- 'make',
- 'BOARD=' + board_name,
- '-j',
+ "make",
+ "BOARD=" + board_name,
+ "-j",
]
- logging.debug('Running command: "%s"', ' '.join(cmd))
- subprocess.run(cmd, env=dict(os.environ, CC='clang'), check=True)
+ logging.debug('Running command: "%s"', " ".join(cmd))
+ subprocess.run(cmd, env=dict(os.environ, CC="clang"), check=True)
def main() -> int:
parser = argparse.ArgumentParser()
- log_level_choices = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
- parser.add_argument(
- '--log_level', '-l',
- choices=log_level_choices,
- default='DEBUG'
- )
+ log_level_choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
+ parser.add_argument("--log_level", "-l", choices=log_level_choices, default="DEBUG")
parser.add_argument(
- '--num_threads', '-j',
- type=int,
- default=multiprocessing.cpu_count()
+ "--num_threads", "-j", type=int, default=multiprocessing.cpu_count()
)
args = parser.parse_args()
logging.basicConfig(level=args.log_level)
- logging.debug('Building with %d threads', args.num_threads)
+ logging.debug("Building with %d threads", args.num_threads)
failed_boards = []
with ThreadPoolExecutor(max_workers=args.num_threads) as executor:
@@ -73,13 +66,14 @@ def main() -> int:
failed_boards.append(board)
if len(failed_boards) > 0:
- logging.error('The following boards failed to compile:\n%s',
- '\n'.join(failed_boards))
+ logging.error(
+ "The following boards failed to compile:\n%s", "\n".join(failed_boards)
+ )
return 1
- logging.info('All boards compiled successfully!')
+ logging.info("All boards compiled successfully!")
return 0
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main())
diff --git a/util/chargen b/util/chargen
index 9ba14d3d6a..a1f7947a14 100644
--- a/util/chargen
+++ b/util/chargen
@@ -5,6 +5,7 @@
import sys
+
def chargen(modulo, max_chars):
"""Generate a stream of characters on the console.
@@ -18,7 +19,7 @@ def chargen(modulo, max_chars):
zero, if zero - print indefinitely
"""
- base = '0'
+ base = "0"
c = base
counter = 0
while True:
@@ -26,25 +27,25 @@ def chargen(modulo, max_chars):
counter = counter + 1
if (max_chars != 0) and (counter == max_chars):
- sys.stdout.write('\n')
+ sys.stdout.write("\n")
return
if modulo and ((counter % modulo) == 0):
c = base
continue
- if c == 'z':
+ if c == "z":
c = base
- elif c == 'Z':
- c = 'a'
- elif c == '9':
- c = 'A'
+ elif c == "Z":
+ c = "a"
+ elif c == "9":
+ c = "A"
else:
- c = '%c' % (ord(c) + 1)
+ c = "%c" % (ord(c) + 1)
def main(args):
- '''Process command line arguments and invoke chargen if args are valid'''
+ """Process command line arguments and invoke chargen if args are valid"""
modulo = 0
max_chars = 0
@@ -55,8 +56,7 @@ def main(args):
if len(args) > 1:
max_chars = int(args[1])
except ValueError:
- sys.stderr.write('usage %s:'
- "['seq_length' ['max_chars']]\n")
+ sys.stderr.write("usage %s:" "['seq_length' ['max_chars']]\n")
sys.exit(1)
try:
@@ -64,6 +64,7 @@ def main(args):
except KeyboardInterrupt:
print()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main(sys.argv[1:])
sys.exit(0)
diff --git a/util/config_option_check.py b/util/config_option_check.py
index 8bd8ecb1f0..0b05b64091 100755
--- a/util/config_option_check.py
+++ b/util/config_option_check.py
@@ -13,6 +13,7 @@ Script to ensure that all configuration options for the Chrome EC are defined
in config.h.
"""
from __future__ import print_function
+
import enum
import os
import re
@@ -21,368 +22,392 @@ import sys
class Line(object):
- """Class for each changed line in diff output.
+ """Class for each changed line in diff output.
- Attributes:
- line_num: The integer line number that this line appears in the file.
- string: The literal string of this line.
- line_type: '+' or '-' indicating if this line was an addition or
- deletion.
- """
+ Attributes:
+ line_num: The integer line number that this line appears in the file.
+ string: The literal string of this line.
+ line_type: '+' or '-' indicating if this line was an addition or
+ deletion.
+ """
- def __init__(self, line_num, string, line_type):
- """Inits Line with the line number and the actual string."""
- self.line_num = line_num
- self.string = string
- self.line_type = line_type
+ def __init__(self, line_num, string, line_type):
+ """Inits Line with the line number and the actual string."""
+ self.line_num = line_num
+ self.string = string
+ self.line_type = line_type
class Hunk(object):
- """Class for a git diff hunk.
+ """Class for a git diff hunk.
- Attributes:
- filename: The name of the file that this hunk belongs to.
- lines: A list of Line objects that are a part of this hunk.
- """
+ Attributes:
+ filename: The name of the file that this hunk belongs to.
+ lines: A list of Line objects that are a part of this hunk.
+ """
- def __init__(self, filename, lines):
- """Inits Hunk with the filename and the list of lines of the hunk."""
- self.filename = filename
- self.lines = lines
+ def __init__(self, filename, lines):
+ """Inits Hunk with the filename and the list of lines of the hunk."""
+ self.filename = filename
+ self.lines = lines
# Master file which is supposed to include all CONFIG_xxxx descriptions.
-CONFIG_FILE = 'include/config.h'
+CONFIG_FILE = "include/config.h"
# Specific files which the checker should ignore.
-ALLOWLIST = [CONFIG_FILE, 'util/config_option_check.py']
+ALLOWLIST = [CONFIG_FILE, "util/config_option_check.py"]
# Specific directories which the checker should ignore.
-ALLOW_PATTERN = re.compile('zephyr/.*')
+ALLOW_PATTERN = re.compile("zephyr/.*")
# Specific CONFIG_* flags which the checker should ignore.
-ALLOWLIST_CONFIGS = ['CONFIG_ZTEST']
+ALLOWLIST_CONFIGS = ["CONFIG_ZTEST"]
+
def obtain_current_config_options():
- """Obtains current config options from include/config.h.
-
- Scans through the main config file defined in CONFIG_FILE for all CONFIG_*
- options.
-
- Returns:
- config_options: A list of all the config options in the main CONFIG_FILE.
- """
-
- config_options = []
- config_option_re = re.compile(r'^#(define|undef)\s+(CONFIG_[A-Z0-9_]+)')
- with open(CONFIG_FILE, 'r') as config_file:
- for line in config_file:
- result = config_option_re.search(line)
- if not result:
- continue
- word = result.groups()[1]
- if word not in config_options:
- config_options.append(word)
- return config_options
+ """Obtains current config options from include/config.h.
+
+ Scans through the main config file defined in CONFIG_FILE for all CONFIG_*
+ options.
+
+ Returns:
+ config_options: A list of all the config options in the main CONFIG_FILE.
+ """
+
+ config_options = []
+ config_option_re = re.compile(r"^#(define|undef)\s+(CONFIG_[A-Z0-9_]+)")
+ with open(CONFIG_FILE, "r") as config_file:
+ for line in config_file:
+ result = config_option_re.search(line)
+ if not result:
+ continue
+ word = result.groups()[1]
+ if word not in config_options:
+ config_options.append(word)
+ return config_options
+
def obtain_config_options_in_use():
- """Obtains all the config options in use in the repo.
-
- Scans through the entire repo looking for all CONFIG_* options actively used.
-
- Returns:
- options_in_use: A set of all the config options in use in the repo.
- """
- file_list = []
- cwd = os.getcwd()
- config_option_re = re.compile(r'\b(CONFIG_[a-zA-Z0-9_]+)')
- config_debug_option_re = re.compile(r'\b(CONFIG_DEBUG_[a-zA-Z0-9_]+)')
- options_in_use = set()
- for (dirpath, dirnames, filenames) in os.walk(cwd, topdown=True):
- # Ignore the build and private directories (taken from .gitignore)
- if 'build' in dirnames:
- dirnames.remove('build')
- if 'private' in dirnames:
- dirnames.remove('private')
- for f in filenames:
- # Ignore hidden files.
- if f.startswith('.'):
- continue
- # Only consider C source, assembler, and Make-style files.
- if (os.path.splitext(f)[1] in ('.c', '.h', '.inc', '.S', '.mk') or
- 'Makefile' in f):
- file_list.append(os.path.join(dirpath, f))
-
- # Search through each file and build a set of the CONFIG_* options being
- # used.
-
- for f in file_list:
- if CONFIG_FILE in f:
- continue
- with open(f, 'r') as cur_file:
- for line in cur_file:
- match = config_option_re.findall(line)
- if match:
- for option in match:
- if not in_comment(f, line, option):
- if option not in options_in_use:
- options_in_use.add(option)
-
- # Since debug options can be turned on at any time, assume that they are
- # always in use in case any aren't being used.
-
- with open(CONFIG_FILE, 'r') as config_file:
- for line in config_file:
- match = config_debug_option_re.findall(line)
- if match:
- for option in match:
- if not in_comment(CONFIG_FILE, line, option):
- if option not in options_in_use:
- options_in_use.add(option)
-
- return options_in_use
+ """Obtains all the config options in use in the repo.
+
+ Scans through the entire repo looking for all CONFIG_* options actively used.
+
+ Returns:
+ options_in_use: A set of all the config options in use in the repo.
+ """
+ file_list = []
+ cwd = os.getcwd()
+ config_option_re = re.compile(r"\b(CONFIG_[a-zA-Z0-9_]+)")
+ config_debug_option_re = re.compile(r"\b(CONFIG_DEBUG_[a-zA-Z0-9_]+)")
+ options_in_use = set()
+ for (dirpath, dirnames, filenames) in os.walk(cwd, topdown=True):
+ # Ignore the build and private directories (taken from .gitignore)
+ if "build" in dirnames:
+ dirnames.remove("build")
+ if "private" in dirnames:
+ dirnames.remove("private")
+ for f in filenames:
+ # Ignore hidden files.
+ if f.startswith("."):
+ continue
+ # Only consider C source, assembler, and Make-style files.
+ if (
+ os.path.splitext(f)[1] in (".c", ".h", ".inc", ".S", ".mk")
+ or "Makefile" in f
+ ):
+ file_list.append(os.path.join(dirpath, f))
+
+ # Search through each file and build a set of the CONFIG_* options being
+ # used.
+
+ for f in file_list:
+ if CONFIG_FILE in f:
+ continue
+ with open(f, "r") as cur_file:
+ for line in cur_file:
+ match = config_option_re.findall(line)
+ if match:
+ for option in match:
+ if not in_comment(f, line, option):
+ if option not in options_in_use:
+ options_in_use.add(option)
+
+ # Since debug options can be turned on at any time, assume that they are
+ # always in use in case any aren't being used.
+
+ with open(CONFIG_FILE, "r") as config_file:
+ for line in config_file:
+ match = config_debug_option_re.findall(line)
+ if match:
+ for option in match:
+ if not in_comment(CONFIG_FILE, line, option):
+ if option not in options_in_use:
+ options_in_use.add(option)
+
+ return options_in_use
+
def print_missing_config_options(hunks, config_options):
- """Searches thru all the changes in hunks for missing options and prints them.
-
- Args:
- hunks: A list of Hunk objects which represent the hunks from the git
- diff output.
- config_options: A list of all the config options in the main CONFIG_FILE.
-
- Returns:
- missing_config_option: A boolean indicating if any CONFIG_* options
- are missing from the main CONFIG_FILE in this commit or if any CONFIG_*
- options removed are no longer being used in the repo.
- """
- missing_config_option = False
- print_banner = True
- deprecated_options = set()
- # Determine longest CONFIG_* length to be used for formatting.
- max_option_length = max(len(option) for option in config_options)
- config_option_re = re.compile(r'\b(CONFIG_[a-zA-Z0-9_]+)')
-
- # Search for all CONFIG_* options in use in the repo.
- options_in_use = obtain_config_options_in_use()
-
- # Check each hunk's line for a missing config option.
- for h in hunks:
- for l in h.lines:
- # Check for the existence of a CONFIG_* in the line.
- match = filter(lambda opt: opt in ALLOWLIST_CONFIGS,
- config_option_re.findall(l.string))
- if not match:
- continue
-
- # At this point, an option was found in the line. However, we need to
- # verify that it is not within a comment.
- violations = set()
-
- for option in match:
- if not in_comment(h.filename, l.string, option):
- # Since the CONFIG_* option is not within a comment, we've found a
- # violation. We now need to determine if this line is a deletion or
- # not. For deletions, we will need to verify if this CONFIG_* option
- # is no longer being used in the entire repo.
-
- if l.line_type == '-':
- if option not in options_in_use and option in config_options:
- deprecated_options.add(option)
- else:
- violations.add(option)
-
- # Check to see if the CONFIG_* option is in the config file and print the
- # violations.
- for option in match:
- if option not in config_options and option in violations:
- # Print the banner once.
- if print_banner:
- print('The following config options were found to be missing '
- 'from %s.\n'
- 'Please add new config options there along with '
- 'descriptions.\n\n' % CONFIG_FILE)
- print_banner = False
+ """Searches thru all the changes in hunks for missing options and prints them.
+
+ Args:
+ hunks: A list of Hunk objects which represent the hunks from the git
+ diff output.
+ config_options: A list of all the config options in the main CONFIG_FILE.
+
+ Returns:
+ missing_config_option: A boolean indicating if any CONFIG_* options
+ are missing from the main CONFIG_FILE in this commit or if any CONFIG_*
+ options removed are no longer being used in the repo.
+ """
+ missing_config_option = False
+ print_banner = True
+ deprecated_options = set()
+ # Determine longest CONFIG_* length to be used for formatting.
+ max_option_length = max(len(option) for option in config_options)
+ config_option_re = re.compile(r"\b(CONFIG_[a-zA-Z0-9_]+)")
+
+ # Search for all CONFIG_* options in use in the repo.
+ options_in_use = obtain_config_options_in_use()
+
+ # Check each hunk's line for a missing config option.
+ for h in hunks:
+ for l in h.lines:
+ # Check for the existence of a CONFIG_* in the line.
+ match = filter(
+ lambda opt: opt in ALLOWLIST_CONFIGS, config_option_re.findall(l.string)
+ )
+ if not match:
+ continue
+
+ # At this point, an option was found in the line. However, we need to
+ # verify that it is not within a comment.
+ violations = set()
+
+ for option in match:
+ if not in_comment(h.filename, l.string, option):
+ # Since the CONFIG_* option is not within a comment, we've found a
+ # violation. We now need to determine if this line is a deletion or
+ # not. For deletions, we will need to verify if this CONFIG_* option
+ # is no longer being used in the entire repo.
+
+ if l.line_type == "-":
+ if option not in options_in_use and option in config_options:
+ deprecated_options.add(option)
+ else:
+ violations.add(option)
+
+ # Check to see if the CONFIG_* option is in the config file and print the
+ # violations.
+ for option in match:
+ if option not in config_options and option in violations:
+ # Print the banner once.
+ if print_banner:
+ print(
+ "The following config options were found to be missing "
+ "from %s.\n"
+ "Please add new config options there along with "
+ "descriptions.\n\n" % CONFIG_FILE
+ )
+ print_banner = False
+ missing_config_option = True
+ # Print the misssing config option.
+ print(
+ "> %-*s %s:%s"
+ % (max_option_length, option, h.filename, l.line_num)
+ )
+
+ if deprecated_options:
+ print(
+ "\n\nThe following config options are being removed and also appear"
+ " to be the last uses\nof that option. Please remove these "
+ "options from %s.\n\n" % CONFIG_FILE
+ )
+ for option in deprecated_options:
+ print("> %s" % option)
missing_config_option = True
- # Print the misssing config option.
- print('> %-*s %s:%s' % (max_option_length, option,
- h.filename,
- l.line_num))
- if deprecated_options:
- print('\n\nThe following config options are being removed and also appear'
- ' to be the last uses\nof that option. Please remove these '
- 'options from %s.\n\n' % CONFIG_FILE)
- for option in deprecated_options:
- print('> %s' % option)
- missing_config_option = True
+ return missing_config_option
- return missing_config_option
def in_comment(filename, line, substr):
- """Checks if given substring appears in a comment.
-
- Args:
- filename: The filename where this line is from. This is used to determine
- what kind of comments to look for.
- line: String of line to search in.
- substr: Substring to search for in the line.
-
- Returns:
- is_in_comment: Boolean indicating if substr was in a comment.
- """
-
- c_style_ext = ('.c', '.h', '.inc', '.S')
- make_style_ext = ('.mk')
- is_in_comment = False
-
- extension = os.path.splitext(filename)[1]
- substr_idx = line.find(substr)
-
- # Different files have different comment syntax; Handle appropriately.
- if extension in c_style_ext:
- beg_comment_idx = line.find('/*')
- end_comment_idx = line.find('*/')
- if end_comment_idx == -1:
- end_comment_idx = len(line)
-
- if beg_comment_idx == -1:
- # Check to see if this line is from a multi-line comment.
- if line.lstrip().startswith('* '):
- # It _seems_ like it is.
- is_in_comment = True
- else:
- # Check to see if its actually inside the comment.
- if beg_comment_idx < substr_idx < end_comment_idx:
- is_in_comment = True
- elif extension in make_style_ext or 'Makefile' in filename:
- beg_comment_idx = line.find('#')
- # Ignore everything to the right of the hash.
- if beg_comment_idx < substr_idx and beg_comment_idx != -1:
- is_in_comment = True
- return is_in_comment
+ """Checks if given substring appears in a comment.
+
+ Args:
+ filename: The filename where this line is from. This is used to determine
+ what kind of comments to look for.
+ line: String of line to search in.
+ substr: Substring to search for in the line.
+
+ Returns:
+ is_in_comment: Boolean indicating if substr was in a comment.
+ """
+
+ c_style_ext = (".c", ".h", ".inc", ".S")
+ make_style_ext = ".mk"
+ is_in_comment = False
+
+ extension = os.path.splitext(filename)[1]
+ substr_idx = line.find(substr)
+
+ # Different files have different comment syntax; Handle appropriately.
+ if extension in c_style_ext:
+ beg_comment_idx = line.find("/*")
+ end_comment_idx = line.find("*/")
+ if end_comment_idx == -1:
+ end_comment_idx = len(line)
+
+ if beg_comment_idx == -1:
+ # Check to see if this line is from a multi-line comment.
+ if line.lstrip().startswith("* "):
+ # It _seems_ like it is.
+ is_in_comment = True
+ else:
+ # Check to see if its actually inside the comment.
+ if beg_comment_idx < substr_idx < end_comment_idx:
+ is_in_comment = True
+ elif extension in make_style_ext or "Makefile" in filename:
+ beg_comment_idx = line.find("#")
+ # Ignore everything to the right of the hash.
+ if beg_comment_idx < substr_idx and beg_comment_idx != -1:
+ is_in_comment = True
+ return is_in_comment
+
def get_hunks():
- """Gets the hunks of the most recent commit.
-
- States:
- new_file: Searching for a new file in the git diff.
- filename_search: Searching for the filename of this hunk.
- hunk: Searching for the beginning of a new hunk.
- lines: Counting line numbers and searching for changes.
-
- Returns:
- hunks: A list of Hunk objects which represent the hunks in the git diff
- output.
- """
-
- diff = []
- hunks = []
- hunk_lines = []
- line = ''
- filename = ''
- i = 0
- line_num = 0
-
- # Regex patterns
- new_file_re = re.compile(r'^diff --git')
- filename_re = re.compile(r'^[+]{3} (.*)')
- hunk_line_num_re = re.compile(r'^@@ -[0-9]+,[0-9]+ \+([0-9]+),[0-9]+ @@.*')
- line_re = re.compile(r'^([+| |-])(.*)')
-
- # Get the diff output.
- proc = subprocess.run(['git', 'diff', '--cached', '-GCONFIG_*', '--no-prefix',
- '--no-ext-diff', 'HEAD~1'],
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True)
- diff = proc.stdout.splitlines()
- if not diff:
- return []
- line = diff[0]
-
- state = enum.Enum('state', 'NEW_FILE FILENAME_SEARCH HUNK LINES')
- current_state = state.NEW_FILE
-
- while True:
- # Search for the beginning of a new file.
- if current_state is state.NEW_FILE:
- match = new_file_re.search(line)
- if match:
- current_state = state.FILENAME_SEARCH
-
- # Search the diff output for a file name.
- elif current_state is state.FILENAME_SEARCH:
- # Search for a file name.
- match = filename_re.search(line)
- if match:
- filename = match.groups(1)[0]
- if filename in ALLOWLIST or ALLOW_PATTERN.match(filename):
- # Skip the file if it's allowlisted.
- current_state = state.NEW_FILE
- else:
- current_state = state.HUNK
-
- # Search for a hunk. Each hunk starts with a line describing the line
- # numbers in the file.
- elif current_state is state.HUNK:
- hunk_lines = []
- match = hunk_line_num_re.search(line)
- if match:
- # Extract the line number offset.
- line_num = int(match.groups(1)[0])
- current_state = state.LINES
-
- # Start looking for changes.
- elif current_state is state.LINES:
- # Check if state needs updating.
- new_hunk = hunk_line_num_re.search(line)
- new_file = new_file_re.search(line)
- if new_hunk:
- current_state = state.HUNK
- hunks.append(Hunk(filename, hunk_lines))
- continue
- elif new_file:
- current_state = state.NEW_FILE
- hunks.append(Hunk(filename, hunk_lines))
- continue
-
- match = line_re.search(line)
- if match:
- line_type = match.groups(1)[0]
- # We only care about modifications.
- if line_type != ' ':
- hunk_lines.append(Line(line_num, match.groups(2)[1], line_type))
- # Deletions don't count towards the line numbers.
- if line_type != '-':
- line_num += 1
-
- # Advance to the next line
- try:
- i += 1
- line = diff[i]
- except IndexError:
- # We've reached the end of the diff. Return what we have.
- if hunk_lines:
- hunks.append(Hunk(filename, hunk_lines))
- return hunks
+ """Gets the hunks of the most recent commit.
+
+ States:
+ new_file: Searching for a new file in the git diff.
+ filename_search: Searching for the filename of this hunk.
+ hunk: Searching for the beginning of a new hunk.
+ lines: Counting line numbers and searching for changes.
+
+ Returns:
+ hunks: A list of Hunk objects which represent the hunks in the git diff
+ output.
+ """
+
+ diff = []
+ hunks = []
+ hunk_lines = []
+ line = ""
+ filename = ""
+ i = 0
+ line_num = 0
+
+ # Regex patterns
+ new_file_re = re.compile(r"^diff --git")
+ filename_re = re.compile(r"^[+]{3} (.*)")
+ hunk_line_num_re = re.compile(r"^@@ -[0-9]+,[0-9]+ \+([0-9]+),[0-9]+ @@.*")
+ line_re = re.compile(r"^([+| |-])(.*)")
+
+ # Get the diff output.
+ proc = subprocess.run(
+ [
+ "git",
+ "diff",
+ "--cached",
+ "-GCONFIG_*",
+ "--no-prefix",
+ "--no-ext-diff",
+ "HEAD~1",
+ ],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ check=True,
+ )
+ diff = proc.stdout.splitlines()
+ if not diff:
+ return []
+ line = diff[0]
+
+ state = enum.Enum("state", "NEW_FILE FILENAME_SEARCH HUNK LINES")
+ current_state = state.NEW_FILE
+
+ while True:
+ # Search for the beginning of a new file.
+ if current_state is state.NEW_FILE:
+ match = new_file_re.search(line)
+ if match:
+ current_state = state.FILENAME_SEARCH
+
+ # Search the diff output for a file name.
+ elif current_state is state.FILENAME_SEARCH:
+ # Search for a file name.
+ match = filename_re.search(line)
+ if match:
+ filename = match.groups(1)[0]
+ if filename in ALLOWLIST or ALLOW_PATTERN.match(filename):
+ # Skip the file if it's allowlisted.
+ current_state = state.NEW_FILE
+ else:
+ current_state = state.HUNK
+
+ # Search for a hunk. Each hunk starts with a line describing the line
+ # numbers in the file.
+ elif current_state is state.HUNK:
+ hunk_lines = []
+ match = hunk_line_num_re.search(line)
+ if match:
+ # Extract the line number offset.
+ line_num = int(match.groups(1)[0])
+ current_state = state.LINES
+
+ # Start looking for changes.
+ elif current_state is state.LINES:
+ # Check if state needs updating.
+ new_hunk = hunk_line_num_re.search(line)
+ new_file = new_file_re.search(line)
+ if new_hunk:
+ current_state = state.HUNK
+ hunks.append(Hunk(filename, hunk_lines))
+ continue
+ elif new_file:
+ current_state = state.NEW_FILE
+ hunks.append(Hunk(filename, hunk_lines))
+ continue
+
+ match = line_re.search(line)
+ if match:
+ line_type = match.groups(1)[0]
+ # We only care about modifications.
+ if line_type != " ":
+ hunk_lines.append(Line(line_num, match.groups(2)[1], line_type))
+ # Deletions don't count towards the line numbers.
+ if line_type != "-":
+ line_num += 1
+
+ # Advance to the next line
+ try:
+ i += 1
+ line = diff[i]
+ except IndexError:
+ # We've reached the end of the diff. Return what we have.
+ if hunk_lines:
+ hunks.append(Hunk(filename, hunk_lines))
+ return hunks
+
def main():
- """Searches through committed changes for missing config options.
-
- Checks through committed changes for CONFIG_* options. Then checks to make
- sure that all CONFIG_* options used are defined in include/config.h. Finally,
- reports any missing config options.
- """
- # Obtain the hunks of the commit to search through.
- hunks = get_hunks()
- # Obtain config options from include/config.h.
- config_options = obtain_current_config_options()
- # Find any missing config options from the hunks and print them.
- missing_opts = print_missing_config_options(hunks, config_options)
-
- if missing_opts:
- print('\nIt may also be possible that you have a typo.')
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
+ """Searches through committed changes for missing config options.
+
+ Checks through committed changes for CONFIG_* options. Then checks to make
+ sure that all CONFIG_* options used are defined in include/config.h. Finally,
+ reports any missing config options.
+ """
+ # Obtain the hunks of the commit to search through.
+ hunks = get_hunks()
+ # Obtain config options from include/config.h.
+ config_options = obtain_current_config_options()
+ # Find any missing config options from the hunks and print them.
+ missing_opts = print_missing_config_options(hunks, config_options)
+
+ if missing_opts:
+ print("\nIt may also be possible that you have a typo.")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/ec3po/console.py b/util/ec3po/console.py
index e71216e3f2..33fa5a6775 100755
--- a/util/ec3po/console.py
+++ b/util/ec3po/console.py
@@ -17,7 +17,6 @@ from __future__ import print_function
import argparse
import binascii
import ctypes
-from datetime import datetime
import logging
import os
import pty
@@ -26,26 +25,25 @@ import select
import stat
import sys
import traceback
+from datetime import datetime
import six
+from ec3po import interpreter, threadproc_shim
-from ec3po import interpreter
-from ec3po import threadproc_shim
-
-
-PROMPT = b'> '
+PROMPT = b"> "
CONSOLE_INPUT_LINE_SIZE = 80 # Taken from the CONFIG_* with the same name.
CONSOLE_MAX_READ = 100 # Max bytes to read at a time from the user.
LOOK_BUFFER_SIZE = 256 # Size of search window when looking for the enhanced EC
- # image string.
+# image string.
# In console_init(), the EC will print a string saying that the EC console is
# enabled. Enhanced images will print a slightly different string. These
# regular expressions are used to determine at reboot whether the EC image is
# enhanced or not.
-ENHANCED_IMAGE_RE = re.compile(br'Enhanced Console is enabled '
- br'\(v([0-9]+\.[0-9]+\.[0-9]+)\)')
-NON_ENHANCED_IMAGE_RE = re.compile(br'Console is enabled; ')
+ENHANCED_IMAGE_RE = re.compile(
+ rb"Enhanced Console is enabled " rb"\(v([0-9]+\.[0-9]+\.[0-9]+)\)"
+)
+NON_ENHANCED_IMAGE_RE = re.compile(rb"Console is enabled; ")
# The timeouts are really only useful for enhanced EC images, but otherwise just
# serve as a delay for non-enhanced EC images. Therefore, we can keep this
@@ -54,1118 +52,1173 @@ NON_ENHANCED_IMAGE_RE = re.compile(br'Console is enabled; ')
# EC image, we can increase the timeout for stability just in case it takes a
# bit longer to receive an ACK for some reason.
NON_ENHANCED_EC_INTERROGATION_TIMEOUT = 0.3 # Maximum number of seconds to wait
- # for a response to an
- # interrogation of a non-enhanced
- # EC image.
+# for a response to an
+# interrogation of a non-enhanced
+# EC image.
ENHANCED_EC_INTERROGATION_TIMEOUT = 1.0 # Maximum number of seconds to wait for
- # a response to an interrogation of an
- # enhanced EC image.
+# a response to an interrogation of an
+# enhanced EC image.
# List of modes which control when interrogations are performed with the EC.
-INTERROGATION_MODES = [b'never', b'always', b'auto']
+INTERROGATION_MODES = [b"never", b"always", b"auto"]
# Format for printing host timestamp
-HOST_STRFTIME="%y-%m-%d %H:%M:%S.%f"
+HOST_STRFTIME = "%y-%m-%d %H:%M:%S.%f"
class EscState(object):
- """Class which contains an enumeration for states of ESC sequences."""
- ESC_START = 1
- ESC_BRACKET = 2
- ESC_BRACKET_1 = 3
- ESC_BRACKET_3 = 4
- ESC_BRACKET_8 = 5
-
+ """Class which contains an enumeration for states of ESC sequences."""
-class ControlKey(object):
- """Class which contains codes for various control keys."""
- BACKSPACE = 0x08
- CTRL_A = 0x01
- CTRL_B = 0x02
- CTRL_D = 0x04
- CTRL_E = 0x05
- CTRL_F = 0x06
- CTRL_K = 0x0b
- CTRL_N = 0xe
- CTRL_P = 0x10
- CARRIAGE_RETURN = 0x0d
- ESC = 0x1b
+ ESC_START = 1
+ ESC_BRACKET = 2
+ ESC_BRACKET_1 = 3
+ ESC_BRACKET_3 = 4
+ ESC_BRACKET_8 = 5
-class Console(object):
- """Class which provides the console interface between the EC and the user.
-
- This class essentially represents the console interface between the user and
- the EC. It handles all of the console editing behaviour
-
- Attributes:
- logger: A logger for this module.
- controller_pty: File descriptor to the controller side of the PTY. Used for
- driving output to the user and receiving user input.
- user_pty: A string representing the PTY name of the served console.
- cmd_pipe: A socket.socket or multiprocessing.Connection object which
- represents the console side of the command pipe. This must be a
- bidirectional pipe. Console commands and responses utilize this pipe.
- dbg_pipe: A socket.socket or multiprocessing.Connection object which
- represents the console's read-only side of the debug pipe. This must be a
- unidirectional pipe attached to the intepreter. EC debug messages use
- this pipe.
- oobm_queue: A queue.Queue or multiprocessing.Queue which is used for out of
- band management for the interactive console.
- input_buffer: A string representing the current input command.
- input_buffer_pos: An integer representing the current position in the buffer
- to insert a char.
- partial_cmd: A string representing the command entered on a line before
- pressing the up arrow keys.
- esc_state: An integer represeting the current state within an escape
- sequence.
- line_limit: An integer representing the maximum number of characters on a
- line.
- history: A list of strings containing the past entered console commands.
- history_pos: An integer representing the current history buffer position.
- This index is used to show previous commands.
- prompt: A string representing the console prompt displayed to the user.
- enhanced_ec: A boolean indicating if the EC image that we are currently
- communicating with is enhanced or not. Enhanced EC images will support
- packed commands and host commands over the UART. This defaults to False
- until we perform some handshaking.
- interrogation_timeout: A float representing the current maximum seconds to
- wait for a response to an interrogation.
- receiving_oobm_cmd: A boolean indicating whether or not the console is in
- the middle of receiving an out of band command.
- pending_oobm_cmd: A string containing the pending OOBM command.
- interrogation_mode: A string containing the current mode of whether
- interrogations are performed with the EC or not and how often.
- raw_debug: Flag to indicate whether per interrupt data should be logged to
- debug
- output_line_log_buffer: buffer for lines coming from the EC to log to debug
- """
-
- def __init__(self, controller_pty, user_pty, interface_pty, cmd_pipe, dbg_pipe,
- name=None):
- """Initalises a Console object with the provided arguments.
+class ControlKey(object):
+ """Class which contains codes for various control keys."""
- Args:
- controller_pty: File descriptor to the controller side of the PTY. Used for
- driving output to the user and receiving user input.
- user_pty: A string representing the PTY name of the served console.
- interface_pty: A string representing the PTY name of the served command
- interface.
- cmd_pipe: A socket.socket or multiprocessing.Connection object which
- represents the console side of the command pipe. This must be a
- bidirectional pipe. Console commands and responses utilize this pipe.
- dbg_pipe: A socket.socket or multiprocessing.Connection object which
- represents the console's read-only side of the debug pipe. This must be a
- unidirectional pipe attached to the intepreter. EC debug messages use
- this pipe.
- name: the console source name
- """
- # Create a unique logger based on the console name
- console_prefix = ('%s - ' % name) if name else ''
- logger = logging.getLogger('%sEC3PO.Console' % console_prefix)
- self.logger = interpreter.LoggerAdapter(logger, {'pty': user_pty})
- self.controller_pty = controller_pty
- self.user_pty = user_pty
- self.interface_pty = interface_pty
- self.cmd_pipe = cmd_pipe
- self.dbg_pipe = dbg_pipe
- self.oobm_queue = threadproc_shim.Queue()
- self.input_buffer = b''
- self.input_buffer_pos = 0
- self.partial_cmd = b''
- self.esc_state = 0
- self.line_limit = CONSOLE_INPUT_LINE_SIZE
- self.history = []
- self.history_pos = 0
- self.prompt = PROMPT
- self.enhanced_ec = False
- self.interrogation_timeout = NON_ENHANCED_EC_INTERROGATION_TIMEOUT
- self.receiving_oobm_cmd = False
- self.pending_oobm_cmd = b''
- self.interrogation_mode = b'auto'
- self.timestamp_enabled = True
- self.look_buffer = b''
- self.raw_debug = False
- self.output_line_log_buffer = []
-
- def __str__(self):
- """Show internal state of Console object as a string."""
- string = []
- string.append('controller_pty: %s' % self.controller_pty)
- string.append('user_pty: %s' % self.user_pty)
- string.append('interface_pty: %s' % self.interface_pty)
- string.append('cmd_pipe: %s' % self.cmd_pipe)
- string.append('dbg_pipe: %s' % self.dbg_pipe)
- string.append('oobm_queue: %s' % self.oobm_queue)
- string.append('input_buffer: %s' % self.input_buffer)
- string.append('input_buffer_pos: %d' % self.input_buffer_pos)
- string.append('esc_state: %d' % self.esc_state)
- string.append('line_limit: %d' % self.line_limit)
- string.append('history: %r' % self.history)
- string.append('history_pos: %d' % self.history_pos)
- string.append('prompt: %r' % self.prompt)
- string.append('partial_cmd: %r'% self.partial_cmd)
- string.append('interrogation_mode: %r' % self.interrogation_mode)
- string.append('look_buffer: %r' % self.look_buffer)
- return '\n'.join(string)
-
- def LogConsoleOutput(self, data):
- """Log to debug user MCU output to controller_pty when line is filled.
-
- The logging also suppresses the Cr50 spinner lines by removing characters
- when it sees backspaces.
+ BACKSPACE = 0x08
+ CTRL_A = 0x01
+ CTRL_B = 0x02
+ CTRL_D = 0x04
+ CTRL_E = 0x05
+ CTRL_F = 0x06
+ CTRL_K = 0x0B
+ CTRL_N = 0xE
+ CTRL_P = 0x10
+ CARRIAGE_RETURN = 0x0D
+ ESC = 0x1B
- Args:
- data: bytes - string received from MCU
- """
- data = list(data)
- # For compatibility with python2 and python3, standardize on the data
- # being a list of integers. This requires one more transformation in py2
- if not isinstance(data[0], int):
- data = [ord(c) for c in data]
-
- # This is a list of already filtered characters (or placeholders).
- line = self.output_line_log_buffer
-
- # TODO(b/177480273): use raw strings here
- symbols = {
- ord(b'\n'): u'\\n',
- ord(b'\r'): u'\\r',
- ord(b'\t'): u'\\t'
- }
- # self.logger.debug(u'%s + %r', u''.join(line), ''.join(data))
- while data:
- # Recall, data is a list of integers, namely the byte values sent by
- # the MCU.
- byte = data.pop(0)
- # This means that |byte| is an int.
- if byte == ord('\n'):
- line.append(symbols[byte])
- if line:
- self.logger.debug(u'%s', ''.join(line))
- line = []
- elif byte == ord('\b'):
- # Backspace: trim the last character off the buffer
- if line:
- line.pop(-1)
- elif byte in symbols:
- line.append(symbols[byte])
- elif byte < ord(' ') or byte > ord('~'):
- # Turn any character that isn't printable ASCII into escaped hex.
- # ' ' is chr(20), and 0-19 are unprintable control characters.
- # '~' is chr(126), and 127 is DELETE. 128-255 are control and Latin-1.
- line.append(u'\\x%02x' % byte)
- else:
- # byte is printable. Thus it is safe to use chr() to get the printable
- # character out of it again.
- line.append(u'%s' % chr(byte))
- self.output_line_log_buffer = line
-
- def PrintHistory(self):
- """Print the history of entered commands."""
- fd = self.controller_pty
- # Make it pretty by figuring out how wide to pad the numbers.
- wide = (len(self.history) // 10) + 1
- for i in range(len(self.history)):
- line = b' %*d %s\r\n' % (wide, i, self.history[i])
- os.write(fd, line)
-
- def ShowPreviousCommand(self):
- """Shows the previous command from the history list."""
- # There's nothing to do if there's no history at all.
- if not self.history:
- self.logger.debug('No history to print.')
- return
-
- # Don't do anything if there's no more history to show.
- if self.history_pos == 0:
- self.logger.debug('No more history to show.')
- return
-
- self.logger.debug('current history position: %d.', self.history_pos)
-
- # Decrement the history buffer position.
- self.history_pos -= 1
- self.logger.debug('new history position.: %d', self.history_pos)
-
- # Save the text entered on the console if any.
- if self.history_pos == len(self.history)-1:
- self.logger.debug('saving partial_cmd: %r', self.input_buffer)
- self.partial_cmd = self.input_buffer
-
- # Backspace the line.
- for _ in range(self.input_buffer_pos):
- self.SendBackspace()
-
- # Print the last entry in the history buffer.
- self.logger.debug('printing previous entry %d - %s', self.history_pos,
- self.history[self.history_pos])
- fd = self.controller_pty
- prev_cmd = self.history[self.history_pos]
- os.write(fd, prev_cmd)
- # Update the input buffer.
- self.input_buffer = prev_cmd
- self.input_buffer_pos = len(prev_cmd)
-
- def ShowNextCommand(self):
- """Shows the next command from the history list."""
- # Don't do anything if there's no history at all.
- if not self.history:
- self.logger.debug('History buffer is empty.')
- return
-
- fd = self.controller_pty
-
- self.logger.debug('current history position: %d', self.history_pos)
- # Increment the history position.
- self.history_pos += 1
-
- # Restore the partial cmd.
- if self.history_pos == len(self.history):
- self.logger.debug('Restoring partial command of %r', self.partial_cmd)
- # Backspace the line.
- for _ in range(self.input_buffer_pos):
- self.SendBackspace()
- # Print the partially entered command if any.
- os.write(fd, self.partial_cmd)
- self.input_buffer = self.partial_cmd
- self.input_buffer_pos = len(self.input_buffer)
- # Now that we've printed it, clear the partial cmd storage.
- self.partial_cmd = b''
- # Reset history position.
- self.history_pos = len(self.history)
- return
-
- self.logger.debug('new history position: %d', self.history_pos)
- if self.history_pos > len(self.history)-1:
- self.logger.debug('No more history to show.')
- self.history_pos -= 1
- self.logger.debug('Reset history position to %d', self.history_pos)
- return
-
- # Backspace the line.
- for _ in range(self.input_buffer_pos):
- self.SendBackspace()
-
- # Print the newer entry from the history buffer.
- self.logger.debug('printing next entry %d - %s', self.history_pos,
- self.history[self.history_pos])
- next_cmd = self.history[self.history_pos]
- os.write(fd, next_cmd)
- # Update the input buffer.
- self.input_buffer = next_cmd
- self.input_buffer_pos = len(next_cmd)
- self.logger.debug('new history position: %d.', self.history_pos)
-
- def SliceOutChar(self):
- """Remove a char from the line and shift everything over 1 column."""
- fd = self.controller_pty
- # Remove the character at the input_buffer_pos by slicing it out.
- self.input_buffer = self.input_buffer[0:self.input_buffer_pos] + \
- self.input_buffer[self.input_buffer_pos+1:]
- # Write the rest of the line
- moved_col = os.write(fd, self.input_buffer[self.input_buffer_pos:])
- # Write a space to clear out the last char
- moved_col += os.write(fd, b' ')
- # Update the input buffer position.
- self.input_buffer_pos += moved_col
- # Reset the cursor
- self.MoveCursor('left', moved_col)
-
- def HandleEsc(self, byte):
- """HandleEsc processes escape sequences.
- Args:
- byte: An integer representing the current byte in the sequence.
+class Console(object):
+ """Class which provides the console interface between the EC and the user.
+
+ This class essentially represents the console interface between the user and
+ the EC. It handles all of the console editing behaviour
+
+ Attributes:
+ logger: A logger for this module.
+ controller_pty: File descriptor to the controller side of the PTY. Used for
+ driving output to the user and receiving user input.
+ user_pty: A string representing the PTY name of the served console.
+ cmd_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the console side of the command pipe. This must be a
+ bidirectional pipe. Console commands and responses utilize this pipe.
+ dbg_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the console's read-only side of the debug pipe. This must be a
+ unidirectional pipe attached to the intepreter. EC debug messages use
+ this pipe.
+ oobm_queue: A queue.Queue or multiprocessing.Queue which is used for out of
+ band management for the interactive console.
+ input_buffer: A string representing the current input command.
+ input_buffer_pos: An integer representing the current position in the buffer
+ to insert a char.
+ partial_cmd: A string representing the command entered on a line before
+ pressing the up arrow keys.
+ esc_state: An integer represeting the current state within an escape
+ sequence.
+ line_limit: An integer representing the maximum number of characters on a
+ line.
+ history: A list of strings containing the past entered console commands.
+ history_pos: An integer representing the current history buffer position.
+ This index is used to show previous commands.
+ prompt: A string representing the console prompt displayed to the user.
+ enhanced_ec: A boolean indicating if the EC image that we are currently
+ communicating with is enhanced or not. Enhanced EC images will support
+ packed commands and host commands over the UART. This defaults to False
+ until we perform some handshaking.
+ interrogation_timeout: A float representing the current maximum seconds to
+ wait for a response to an interrogation.
+ receiving_oobm_cmd: A boolean indicating whether or not the console is in
+ the middle of receiving an out of band command.
+ pending_oobm_cmd: A string containing the pending OOBM command.
+ interrogation_mode: A string containing the current mode of whether
+ interrogations are performed with the EC or not and how often.
+ raw_debug: Flag to indicate whether per interrupt data should be logged to
+ debug
+ output_line_log_buffer: buffer for lines coming from the EC to log to debug
"""
- # We shouldn't be handling an escape sequence if we haven't seen one.
- assert self.esc_state != 0
-
- if self.esc_state is EscState.ESC_START:
- self.logger.debug('ESC_START')
- if byte == ord('['):
- self.esc_state = EscState.ESC_BRACKET
- return
- else:
- self.logger.error('Unexpected sequence. %c', byte)
- self.esc_state = 0
-
- elif self.esc_state is EscState.ESC_BRACKET:
- self.logger.debug('ESC_BRACKET')
- # Left Arrow key was pressed.
- if byte == ord('D'):
- self.logger.debug('Left arrow key pressed.')
- self.MoveCursor('left', 1)
- self.esc_state = 0 # Reset the state.
- return
-
- # Right Arrow key.
- elif byte == ord('C'):
- self.logger.debug('Right arrow key pressed.')
- self.MoveCursor('right', 1)
- self.esc_state = 0 # Reset the state.
- return
-
- # Up Arrow key.
- elif byte == ord('A'):
- self.logger.debug('Up arrow key pressed.')
- self.ShowPreviousCommand()
- # Reset the state.
- self.esc_state = 0 # Reset the state.
- return
-
- # Down Arrow key.
- elif byte == ord('B'):
- self.logger.debug('Down arrow key pressed.')
- self.ShowNextCommand()
- # Reset the state.
- self.esc_state = 0 # Reset the state.
- return
-
- # For some reason, minicom sends a 1 instead of 7. /shrug
- # TODO(aaboagye): Figure out why this happens.
- elif byte == ord('1') or byte == ord('7'):
- self.esc_state = EscState.ESC_BRACKET_1
-
- elif byte == ord('3'):
- self.esc_state = EscState.ESC_BRACKET_3
-
- elif byte == ord('8'):
- self.esc_state = EscState.ESC_BRACKET_8
-
- else:
- self.logger.error(r'Bad or unhandled escape sequence. got ^[%c\(%d)',
- chr(byte), byte)
- self.esc_state = 0
- return
-
- elif self.esc_state is EscState.ESC_BRACKET_1:
- self.logger.debug('ESC_BRACKET_1')
- # HOME key.
- if byte == ord('~'):
- self.logger.debug('Home key pressed.')
- self.MoveCursor('left', self.input_buffer_pos)
- self.esc_state = 0 # Reset the state.
- self.logger.debug('ESC sequence complete.')
- return
-
- elif self.esc_state is EscState.ESC_BRACKET_3:
- self.logger.debug('ESC_BRACKET_3')
- # DEL key.
- if byte == ord('~'):
- self.logger.debug('Delete key pressed.')
- if self.input_buffer_pos != len(self.input_buffer):
- self.SliceOutChar()
- self.esc_state = 0 # Reset the state.
-
- elif self.esc_state is EscState.ESC_BRACKET_8:
- self.logger.debug('ESC_BRACKET_8')
- # END key.
- if byte == ord('~'):
- self.logger.debug('End key pressed.')
- self.MoveCursor('right',
- len(self.input_buffer) - self.input_buffer_pos)
- self.esc_state = 0 # Reset the state.
- self.logger.debug('ESC sequence complete.')
- return
-
- else:
- self.logger.error('Unexpected sequence. %c', byte)
+ def __init__(
+ self, controller_pty, user_pty, interface_pty, cmd_pipe, dbg_pipe, name=None
+ ):
+ """Initalises a Console object with the provided arguments.
+
+ Args:
+ controller_pty: File descriptor to the controller side of the PTY. Used for
+ driving output to the user and receiving user input.
+ user_pty: A string representing the PTY name of the served console.
+ interface_pty: A string representing the PTY name of the served command
+ interface.
+ cmd_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the console side of the command pipe. This must be a
+ bidirectional pipe. Console commands and responses utilize this pipe.
+ dbg_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the console's read-only side of the debug pipe. This must be a
+ unidirectional pipe attached to the intepreter. EC debug messages use
+ this pipe.
+ name: the console source name
+ """
+ # Create a unique logger based on the console name
+ console_prefix = ("%s - " % name) if name else ""
+ logger = logging.getLogger("%sEC3PO.Console" % console_prefix)
+ self.logger = interpreter.LoggerAdapter(logger, {"pty": user_pty})
+ self.controller_pty = controller_pty
+ self.user_pty = user_pty
+ self.interface_pty = interface_pty
+ self.cmd_pipe = cmd_pipe
+ self.dbg_pipe = dbg_pipe
+ self.oobm_queue = threadproc_shim.Queue()
+ self.input_buffer = b""
+ self.input_buffer_pos = 0
+ self.partial_cmd = b""
self.esc_state = 0
+ self.line_limit = CONSOLE_INPUT_LINE_SIZE
+ self.history = []
+ self.history_pos = 0
+ self.prompt = PROMPT
+ self.enhanced_ec = False
+ self.interrogation_timeout = NON_ENHANCED_EC_INTERROGATION_TIMEOUT
+ self.receiving_oobm_cmd = False
+ self.pending_oobm_cmd = b""
+ self.interrogation_mode = b"auto"
+ self.timestamp_enabled = True
+ self.look_buffer = b""
+ self.raw_debug = False
+ self.output_line_log_buffer = []
+
+ def __str__(self):
+ """Show internal state of Console object as a string."""
+ string = []
+ string.append("controller_pty: %s" % self.controller_pty)
+ string.append("user_pty: %s" % self.user_pty)
+ string.append("interface_pty: %s" % self.interface_pty)
+ string.append("cmd_pipe: %s" % self.cmd_pipe)
+ string.append("dbg_pipe: %s" % self.dbg_pipe)
+ string.append("oobm_queue: %s" % self.oobm_queue)
+ string.append("input_buffer: %s" % self.input_buffer)
+ string.append("input_buffer_pos: %d" % self.input_buffer_pos)
+ string.append("esc_state: %d" % self.esc_state)
+ string.append("line_limit: %d" % self.line_limit)
+ string.append("history: %r" % self.history)
+ string.append("history_pos: %d" % self.history_pos)
+ string.append("prompt: %r" % self.prompt)
+ string.append("partial_cmd: %r" % self.partial_cmd)
+ string.append("interrogation_mode: %r" % self.interrogation_mode)
+ string.append("look_buffer: %r" % self.look_buffer)
+ return "\n".join(string)
+
+ def LogConsoleOutput(self, data):
+ """Log to debug user MCU output to controller_pty when line is filled.
+
+ The logging also suppresses the Cr50 spinner lines by removing characters
+ when it sees backspaces.
+
+ Args:
+ data: bytes - string received from MCU
+ """
+ data = list(data)
+ # For compatibility with python2 and python3, standardize on the data
+ # being a list of integers. This requires one more transformation in py2
+ if not isinstance(data[0], int):
+ data = [ord(c) for c in data]
+
+ # This is a list of already filtered characters (or placeholders).
+ line = self.output_line_log_buffer
+
+ # TODO(b/177480273): use raw strings here
+ symbols = {ord(b"\n"): "\\n", ord(b"\r"): "\\r", ord(b"\t"): "\\t"}
+ # self.logger.debug(u'%s + %r', u''.join(line), ''.join(data))
+ while data:
+ # Recall, data is a list of integers, namely the byte values sent by
+ # the MCU.
+ byte = data.pop(0)
+ # This means that |byte| is an int.
+ if byte == ord("\n"):
+ line.append(symbols[byte])
+ if line:
+ self.logger.debug("%s", "".join(line))
+ line = []
+ elif byte == ord("\b"):
+ # Backspace: trim the last character off the buffer
+ if line:
+ line.pop(-1)
+ elif byte in symbols:
+ line.append(symbols[byte])
+ elif byte < ord(" ") or byte > ord("~"):
+ # Turn any character that isn't printable ASCII into escaped hex.
+ # ' ' is chr(20), and 0-19 are unprintable control characters.
+ # '~' is chr(126), and 127 is DELETE. 128-255 are control and Latin-1.
+ line.append("\\x%02x" % byte)
+ else:
+ # byte is printable. Thus it is safe to use chr() to get the printable
+ # character out of it again.
+ line.append("%s" % chr(byte))
+ self.output_line_log_buffer = line
+
+ def PrintHistory(self):
+ """Print the history of entered commands."""
+ fd = self.controller_pty
+ # Make it pretty by figuring out how wide to pad the numbers.
+ wide = (len(self.history) // 10) + 1
+ for i in range(len(self.history)):
+ line = b" %*d %s\r\n" % (wide, i, self.history[i])
+ os.write(fd, line)
+
+ def ShowPreviousCommand(self):
+ """Shows the previous command from the history list."""
+ # There's nothing to do if there's no history at all.
+ if not self.history:
+ self.logger.debug("No history to print.")
+ return
+
+ # Don't do anything if there's no more history to show.
+ if self.history_pos == 0:
+ self.logger.debug("No more history to show.")
+ return
+
+ self.logger.debug("current history position: %d.", self.history_pos)
+
+ # Decrement the history buffer position.
+ self.history_pos -= 1
+ self.logger.debug("new history position.: %d", self.history_pos)
+
+ # Save the text entered on the console if any.
+ if self.history_pos == len(self.history) - 1:
+ self.logger.debug("saving partial_cmd: %r", self.input_buffer)
+ self.partial_cmd = self.input_buffer
+
+ # Backspace the line.
+ for _ in range(self.input_buffer_pos):
+ self.SendBackspace()
+
+ # Print the last entry in the history buffer.
+ self.logger.debug(
+ "printing previous entry %d - %s",
+ self.history_pos,
+ self.history[self.history_pos],
+ )
+ fd = self.controller_pty
+ prev_cmd = self.history[self.history_pos]
+ os.write(fd, prev_cmd)
+ # Update the input buffer.
+ self.input_buffer = prev_cmd
+ self.input_buffer_pos = len(prev_cmd)
+
+ def ShowNextCommand(self):
+ """Shows the next command from the history list."""
+ # Don't do anything if there's no history at all.
+ if not self.history:
+ self.logger.debug("History buffer is empty.")
+ return
+
+ fd = self.controller_pty
+
+ self.logger.debug("current history position: %d", self.history_pos)
+ # Increment the history position.
+ self.history_pos += 1
+
+ # Restore the partial cmd.
+ if self.history_pos == len(self.history):
+ self.logger.debug("Restoring partial command of %r", self.partial_cmd)
+ # Backspace the line.
+ for _ in range(self.input_buffer_pos):
+ self.SendBackspace()
+ # Print the partially entered command if any.
+ os.write(fd, self.partial_cmd)
+ self.input_buffer = self.partial_cmd
+ self.input_buffer_pos = len(self.input_buffer)
+ # Now that we've printed it, clear the partial cmd storage.
+ self.partial_cmd = b""
+ # Reset history position.
+ self.history_pos = len(self.history)
+ return
+
+ self.logger.debug("new history position: %d", self.history_pos)
+ if self.history_pos > len(self.history) - 1:
+ self.logger.debug("No more history to show.")
+ self.history_pos -= 1
+ self.logger.debug("Reset history position to %d", self.history_pos)
+ return
+
+ # Backspace the line.
+ for _ in range(self.input_buffer_pos):
+ self.SendBackspace()
+
+ # Print the newer entry from the history buffer.
+ self.logger.debug(
+ "printing next entry %d - %s",
+ self.history_pos,
+ self.history[self.history_pos],
+ )
+ next_cmd = self.history[self.history_pos]
+ os.write(fd, next_cmd)
+ # Update the input buffer.
+ self.input_buffer = next_cmd
+ self.input_buffer_pos = len(next_cmd)
+ self.logger.debug("new history position: %d.", self.history_pos)
+
+ def SliceOutChar(self):
+ """Remove a char from the line and shift everything over 1 column."""
+ fd = self.controller_pty
+ # Remove the character at the input_buffer_pos by slicing it out.
+ self.input_buffer = (
+ self.input_buffer[0 : self.input_buffer_pos]
+ + self.input_buffer[self.input_buffer_pos + 1 :]
+ )
+ # Write the rest of the line
+ moved_col = os.write(fd, self.input_buffer[self.input_buffer_pos :])
+ # Write a space to clear out the last char
+ moved_col += os.write(fd, b" ")
+ # Update the input buffer position.
+ self.input_buffer_pos += moved_col
+ # Reset the cursor
+ self.MoveCursor("left", moved_col)
+
+ def HandleEsc(self, byte):
+ """HandleEsc processes escape sequences.
+
+ Args:
+ byte: An integer representing the current byte in the sequence.
+ """
+ # We shouldn't be handling an escape sequence if we haven't seen one.
+ assert self.esc_state != 0
+
+ if self.esc_state is EscState.ESC_START:
+ self.logger.debug("ESC_START")
+ if byte == ord("["):
+ self.esc_state = EscState.ESC_BRACKET
+ return
+
+ else:
+ self.logger.error("Unexpected sequence. %c", byte)
+ self.esc_state = 0
+
+ elif self.esc_state is EscState.ESC_BRACKET:
+ self.logger.debug("ESC_BRACKET")
+ # Left Arrow key was pressed.
+ if byte == ord("D"):
+ self.logger.debug("Left arrow key pressed.")
+ self.MoveCursor("left", 1)
+ self.esc_state = 0 # Reset the state.
+ return
+
+ # Right Arrow key.
+ elif byte == ord("C"):
+ self.logger.debug("Right arrow key pressed.")
+ self.MoveCursor("right", 1)
+ self.esc_state = 0 # Reset the state.
+ return
+
+ # Up Arrow key.
+ elif byte == ord("A"):
+ self.logger.debug("Up arrow key pressed.")
+ self.ShowPreviousCommand()
+ # Reset the state.
+ self.esc_state = 0 # Reset the state.
+ return
+
+ # Down Arrow key.
+ elif byte == ord("B"):
+ self.logger.debug("Down arrow key pressed.")
+ self.ShowNextCommand()
+ # Reset the state.
+ self.esc_state = 0 # Reset the state.
+ return
+
+ # For some reason, minicom sends a 1 instead of 7. /shrug
+ # TODO(aaboagye): Figure out why this happens.
+ elif byte == ord("1") or byte == ord("7"):
+ self.esc_state = EscState.ESC_BRACKET_1
+
+ elif byte == ord("3"):
+ self.esc_state = EscState.ESC_BRACKET_3
+
+ elif byte == ord("8"):
+ self.esc_state = EscState.ESC_BRACKET_8
+
+ else:
+ self.logger.error(
+ r"Bad or unhandled escape sequence. got ^[%c\(%d)", chr(byte), byte
+ )
+ self.esc_state = 0
+ return
+
+ elif self.esc_state is EscState.ESC_BRACKET_1:
+ self.logger.debug("ESC_BRACKET_1")
+ # HOME key.
+ if byte == ord("~"):
+ self.logger.debug("Home key pressed.")
+ self.MoveCursor("left", self.input_buffer_pos)
+ self.esc_state = 0 # Reset the state.
+ self.logger.debug("ESC sequence complete.")
+ return
+
+ elif self.esc_state is EscState.ESC_BRACKET_3:
+ self.logger.debug("ESC_BRACKET_3")
+ # DEL key.
+ if byte == ord("~"):
+ self.logger.debug("Delete key pressed.")
+ if self.input_buffer_pos != len(self.input_buffer):
+ self.SliceOutChar()
+ self.esc_state = 0 # Reset the state.
+
+ elif self.esc_state is EscState.ESC_BRACKET_8:
+ self.logger.debug("ESC_BRACKET_8")
+ # END key.
+ if byte == ord("~"):
+ self.logger.debug("End key pressed.")
+ self.MoveCursor("right", len(self.input_buffer) - self.input_buffer_pos)
+ self.esc_state = 0 # Reset the state.
+ self.logger.debug("ESC sequence complete.")
+ return
+
+ else:
+ self.logger.error("Unexpected sequence. %c", byte)
+ self.esc_state = 0
+
+ else:
+ self.logger.error("Unexpected sequence. %c", byte)
+ self.esc_state = 0
+
+ def ProcessInput(self):
+ """Captures the input determines what actions to take."""
+ # There's nothing to do if the input buffer is empty.
+ if len(self.input_buffer) == 0:
+ return
+
+ # Don't store 2 consecutive identical commands in the history.
+ if self.history and self.history[-1] != self.input_buffer or not self.history:
+ self.history.append(self.input_buffer)
+
+ # Split the command up by spaces.
+ line = self.input_buffer.split(b" ")
+ self.logger.debug("cmd: %s", self.input_buffer)
+ cmd = line[0].lower()
+
+ # The 'history' command is a special case that we handle locally.
+ if cmd == "history":
+ self.PrintHistory()
+ return
+
+ # Send the command to the interpreter.
+ self.logger.debug("Sending command to interpreter.")
+ self.cmd_pipe.send(self.input_buffer)
+
+ def CheckForEnhancedECImage(self):
+ """Performs an interrogation of the EC image.
+
+ Send a SYN and expect an ACK. If no ACK or the response is incorrect, then
+ assume that the current EC image that we are talking to is not enhanced.
+
+ Returns:
+ is_enhanced: A boolean indicating whether the EC responded to the
+ interrogation correctly.
+
+ Raises:
+ EOFError: Allowed to propagate through from self.dbg_pipe.recv().
+ """
+ # Send interrogation byte and wait for the response.
+ self.logger.debug("Performing interrogation.")
+ self.cmd_pipe.send(interpreter.EC_SYN)
+
+ response = ""
+ if self.dbg_pipe.poll(self.interrogation_timeout):
+ response = self.dbg_pipe.recv()
+ self.logger.debug("response: %r", binascii.hexlify(response))
+ else:
+ self.logger.debug("Timed out waiting for EC_ACK")
+
+ # Verify the acknowledgment.
+ is_enhanced = response == interpreter.EC_ACK
+
+ if is_enhanced:
+ # Increase the interrogation timeout for stability purposes.
+ self.interrogation_timeout = ENHANCED_EC_INTERROGATION_TIMEOUT
+ self.logger.debug(
+ "Increasing interrogation timeout to %rs.", self.interrogation_timeout
+ )
+ else:
+ # Reduce the timeout in order to reduce the perceivable delay.
+ self.interrogation_timeout = NON_ENHANCED_EC_INTERROGATION_TIMEOUT
+ self.logger.debug(
+ "Reducing interrogation timeout to %rs.", self.interrogation_timeout
+ )
+
+ return is_enhanced
+
+ def HandleChar(self, byte):
+ """HandleChar does a certain action when it receives a character.
+
+ Args:
+ byte: An integer representing the character received from the user.
+
+ Raises:
+ EOFError: Allowed to propagate through from self.CheckForEnhancedECImage()
+ i.e. from self.dbg_pipe.recv().
+ """
+ fd = self.controller_pty
+
+ # Enter the OOBM prompt mode if the user presses '%'.
+ if byte == ord("%"):
+ self.logger.debug("Begin OOBM command.")
+ self.receiving_oobm_cmd = True
+ # Print a "prompt".
+ os.write(self.controller_pty, b"\r\n% ")
+ return
+
+ # Add chars to the pending OOBM command if we're currently receiving one.
+ if self.receiving_oobm_cmd and byte != ControlKey.CARRIAGE_RETURN:
+ tmp_bytes = six.int2byte(byte)
+ self.pending_oobm_cmd += tmp_bytes
+ self.logger.debug("%s", tmp_bytes)
+ os.write(self.controller_pty, tmp_bytes)
+ return
+
+ if byte == ControlKey.CARRIAGE_RETURN:
+ if self.receiving_oobm_cmd:
+ # Terminate the command and place it in the OOBM queue.
+ self.logger.debug("End OOBM command.")
+ if self.pending_oobm_cmd:
+ self.oobm_queue.put(self.pending_oobm_cmd)
+ self.logger.debug(
+ "Placed %r into OOBM command queue.", self.pending_oobm_cmd
+ )
+
+ # Reset the state.
+ os.write(self.controller_pty, b"\r\n" + self.prompt)
+ self.input_buffer = b""
+ self.input_buffer_pos = 0
+ self.receiving_oobm_cmd = False
+ self.pending_oobm_cmd = b""
+ return
+
+ if self.interrogation_mode == b"never":
+ self.logger.debug(
+ "Skipping interrogation because interrogation mode"
+ " is set to never."
+ )
+ elif self.interrogation_mode == b"always":
+ # Only interrogate the EC if the interrogation mode is set to 'always'.
+ self.enhanced_ec = self.CheckForEnhancedECImage()
+ self.logger.debug("Enhanced EC image? %r", self.enhanced_ec)
+
+ if not self.enhanced_ec:
+ # Send everything straight to the EC to handle.
+ self.cmd_pipe.send(six.int2byte(byte))
+ # Reset the input buffer.
+ self.input_buffer = b""
+ self.input_buffer_pos = 0
+ self.logger.log(1, "Reset input buffer.")
+ return
+
+ # Keep handling the ESC sequence if we're in the middle of it.
+ if self.esc_state != 0:
+ self.HandleEsc(byte)
+ return
+
+ # When we're at the end of the line, we should only allow going backwards,
+ # backspace, carriage return, up, or down. The arrow keys are escape
+ # sequences, so we let the escape...escape.
+ if self.input_buffer_pos >= self.line_limit and byte not in [
+ ControlKey.CTRL_B,
+ ControlKey.ESC,
+ ControlKey.BACKSPACE,
+ ControlKey.CTRL_A,
+ ControlKey.CARRIAGE_RETURN,
+ ControlKey.CTRL_P,
+ ControlKey.CTRL_N,
+ ]:
+ return
+
+ # If the input buffer is full we can't accept new chars.
+ buffer_full = len(self.input_buffer) >= self.line_limit
+
+ # Carriage_Return/Enter
+ if byte == ControlKey.CARRIAGE_RETURN:
+ self.logger.debug("Enter key pressed.")
+ # Put a carriage return/newline and the print the prompt.
+ os.write(fd, b"\r\n")
+
+ # TODO(aaboagye): When we control the printing of all output, print the
+ # prompt AFTER printing all the output. We can't do it yet because we
+ # don't know how much is coming from the EC.
+
+ # Print the prompt.
+ os.write(fd, self.prompt)
+ # Process the input.
+ self.ProcessInput()
+ # Now, clear the buffer.
+ self.input_buffer = b""
+ self.input_buffer_pos = 0
+ # Reset history buffer pos.
+ self.history_pos = len(self.history)
+ # Clear partial command.
+ self.partial_cmd = b""
+
+ # Backspace
+ elif byte == ControlKey.BACKSPACE:
+ self.logger.debug("Backspace pressed.")
+ if self.input_buffer_pos > 0:
+ # Move left 1 column.
+ self.MoveCursor("left", 1)
+ # Remove the character at the input_buffer_pos by slicing it out.
+ self.SliceOutChar()
+
+ self.logger.debug("input_buffer_pos: %d", self.input_buffer_pos)
+
+ # Ctrl+A. Move cursor to beginning of the line
+ elif byte == ControlKey.CTRL_A:
+ self.logger.debug("Control+A pressed.")
+ self.MoveCursor("left", self.input_buffer_pos)
+
+ # Ctrl+B. Move cursor left 1 column.
+ elif byte == ControlKey.CTRL_B:
+ self.logger.debug("Control+B pressed.")
+ self.MoveCursor("left", 1)
+
+ # Ctrl+D. Delete a character.
+ elif byte == ControlKey.CTRL_D:
+ self.logger.debug("Control+D pressed.")
+ if self.input_buffer_pos != len(self.input_buffer):
+ # Remove the character by slicing it out.
+ self.SliceOutChar()
+
+ # Ctrl+E. Move cursor to end of the line.
+ elif byte == ControlKey.CTRL_E:
+ self.logger.debug("Control+E pressed.")
+ self.MoveCursor("right", len(self.input_buffer) - self.input_buffer_pos)
+
+ # Ctrl+F. Move cursor right 1 column.
+ elif byte == ControlKey.CTRL_F:
+ self.logger.debug("Control+F pressed.")
+ self.MoveCursor("right", 1)
+
+ # Ctrl+K. Kill line.
+ elif byte == ControlKey.CTRL_K:
+ self.logger.debug("Control+K pressed.")
+ self.KillLine()
+
+ # Ctrl+N. Next line.
+ elif byte == ControlKey.CTRL_N:
+ self.logger.debug("Control+N pressed.")
+ self.ShowNextCommand()
+
+ # Ctrl+P. Previous line.
+ elif byte == ControlKey.CTRL_P:
+ self.logger.debug("Control+P pressed.")
+ self.ShowPreviousCommand()
+
+ # ESC sequence
+ elif byte == ControlKey.ESC:
+ # Starting an ESC sequence
+ self.esc_state = EscState.ESC_START
+
+ # Only print printable chars.
+ elif IsPrintable(byte):
+ # Drop the character if we're full.
+ if buffer_full:
+ self.logger.debug("Dropped char: %c(%d)", byte, byte)
+ return
+ # Print the character.
+ os.write(fd, six.int2byte(byte))
+ # Print the rest of the line (if any).
+ extra_bytes_written = os.write(
+ fd, self.input_buffer[self.input_buffer_pos :]
+ )
+
+ # Recreate the input buffer.
+ self.input_buffer = (
+ self.input_buffer[0 : self.input_buffer_pos]
+ + six.int2byte(byte)
+ + self.input_buffer[self.input_buffer_pos :]
+ )
+ # Update the input buffer position.
+ self.input_buffer_pos += 1 + extra_bytes_written
+
+ # Reset the cursor if we wrote any extra bytes.
+ if extra_bytes_written:
+ self.MoveCursor("left", extra_bytes_written)
+
+ self.logger.debug("input_buffer_pos: %d", self.input_buffer_pos)
+
+ def MoveCursor(self, direction, count):
+ """MoveCursor moves the cursor left or right by count columns.
+
+ Args:
+ direction: A string that should be either 'left' or 'right' representing
+ the direction to move the cursor on the console.
+ count: An integer representing how many columns the cursor should be
+ moved.
+
+ Raises:
+ AssertionError: If the direction is not equal to 'left' or 'right'.
+ """
+ # If there's nothing to move, we're done.
+ if not count:
+ return
+ fd = self.controller_pty
+ seq = b"\033[" + str(count).encode("ascii")
+ if direction == "left":
+ # Bind the movement.
+ if count > self.input_buffer_pos:
+ count = self.input_buffer_pos
+ seq += b"D"
+ self.logger.debug("move cursor left %d", count)
+ self.input_buffer_pos -= count
+
+ elif direction == "right":
+ # Bind the movement.
+ if (count + self.input_buffer_pos) > len(self.input_buffer):
+ count = 0
+ seq += b"C"
+ self.logger.debug("move cursor right %d", count)
+ self.input_buffer_pos += count
+
+ else:
+ raise AssertionError(
+ ("The only valid directions are 'left' and " "'right'")
+ )
+
+ self.logger.debug("input_buffer_pos: %d", self.input_buffer_pos)
+ # Move the cursor.
+ if count != 0:
+ os.write(fd, seq)
+
+ def KillLine(self):
+ """Kill the rest of the line based on the input buffer position."""
+ # Killing the line is killing all the text to the right.
+ diff = len(self.input_buffer) - self.input_buffer_pos
+ self.logger.debug("diff: %d", diff)
+ # Diff shouldn't be negative, but if it is for some reason, let's try to
+ # correct the cursor.
+ if diff < 0:
+ self.logger.warning(
+ "Resetting input buffer position to %d...", len(self.input_buffer)
+ )
+ self.MoveCursor("left", -diff)
+ return
+ if diff:
+ self.MoveCursor("right", diff)
+ for _ in range(diff):
+ self.SendBackspace()
+ self.input_buffer_pos -= diff
+ self.input_buffer = self.input_buffer[0 : self.input_buffer_pos]
+
+ def SendBackspace(self):
+ """Backspace a character on the console."""
+ os.write(self.controller_pty, b"\033[1D \033[1D")
+
+ def ProcessOOBMQueue(self):
+ """Retrieve an item from the OOBM queue and process it."""
+ item = self.oobm_queue.get()
+ self.logger.debug("OOBM cmd: %r", item)
+ cmd = item.split(b" ")
+
+ if cmd[0] == b"loglevel":
+ # An integer is required in order to set the log level.
+ if len(cmd) < 2:
+ self.logger.debug("Insufficient args")
+ self.PrintOOBMHelp()
+ return
+ try:
+ self.logger.debug("Log level change request.")
+ new_log_level = int(cmd[1])
+ self.logger.logger.setLevel(new_log_level)
+ self.logger.info("Log level changed to %d.", new_log_level)
+
+ # Forward the request to the interpreter as well.
+ self.cmd_pipe.send(item)
+ except ValueError:
+ # Ignoring the request if an integer was not provided.
+ self.PrintOOBMHelp()
+
+ elif cmd[0] == b"timestamp":
+ mode = cmd[1].lower()
+ self.timestamp_enabled = mode == b"on"
+ self.logger.info(
+ "%sabling uart timestamps.", "En" if self.timestamp_enabled else "Dis"
+ )
+
+ elif cmd[0] == b"rawdebug":
+ mode = cmd[1].lower()
+ self.raw_debug = mode == b"on"
+ self.logger.info(
+ "%sabling per interrupt debug logs.", "En" if self.raw_debug else "Dis"
+ )
+
+ elif cmd[0] == b"interrogate" and len(cmd) >= 2:
+ enhanced = False
+ mode = cmd[1]
+ if len(cmd) >= 3 and cmd[2] == b"enhanced":
+ enhanced = True
+
+ # Set the mode if correct.
+ if mode in INTERROGATION_MODES:
+ self.interrogation_mode = mode
+ self.logger.debug("Updated interrogation mode to %s.", mode)
+
+ # Update the assumptions of the EC image.
+ self.enhanced_ec = enhanced
+ self.logger.debug("Enhanced EC image is now %r", self.enhanced_ec)
+
+ # Send command to interpreter as well.
+ self.cmd_pipe.send(b"enhanced " + str(self.enhanced_ec).encode("ascii"))
+ else:
+ self.PrintOOBMHelp()
+
+ else:
+ self.PrintOOBMHelp()
+
+ def PrintOOBMHelp(self):
+ """Prints out the OOBM help."""
+ # Print help syntax.
+ os.write(self.controller_pty, b"\r\n" + b"Known OOBM commands:\r\n")
+ os.write(
+ self.controller_pty,
+ b" interrogate <never | always | auto> " b"[enhanced]\r\n",
+ )
+ os.write(self.controller_pty, b" loglevel <int>\r\n")
+
+ def CheckBufferForEnhancedImage(self, data):
+ """Adds data to a look buffer and checks to see for enhanced EC image.
+
+ The EC's console task prints a string upon initialization which says that
+ "Console is enabled; type HELP for help.". The enhanced EC images print a
+ different string as a part of their init. This function searches through a
+ "look" buffer, scanning for the presence of either of those strings and
+ updating the enhanced_ec state accordingly.
+
+ Args:
+ data: A string containing the data sent from the interpreter.
+ """
+ self.look_buffer += data
+
+ # Search the buffer for any of the EC image strings.
+ enhanced_match = re.search(ENHANCED_IMAGE_RE, self.look_buffer)
+ non_enhanced_match = re.search(NON_ENHANCED_IMAGE_RE, self.look_buffer)
+
+ # Update the state if any matches were found.
+ if enhanced_match or non_enhanced_match:
+ if enhanced_match:
+ self.enhanced_ec = True
+ elif non_enhanced_match:
+ self.enhanced_ec = False
+
+ # Inform the interpreter of the result.
+ self.cmd_pipe.send(b"enhanced " + str(self.enhanced_ec).encode("ascii"))
+ self.logger.debug("Enhanced EC image? %r", self.enhanced_ec)
+
+ # Clear look buffer since a match was found.
+ self.look_buffer = b""
+
+ # Move the sliding window.
+ self.look_buffer = self.look_buffer[-LOOK_BUFFER_SIZE:]
- else:
- self.logger.error('Unexpected sequence. %c', byte)
- self.esc_state = 0
-
- def ProcessInput(self):
- """Captures the input determines what actions to take."""
- # There's nothing to do if the input buffer is empty.
- if len(self.input_buffer) == 0:
- return
-
- # Don't store 2 consecutive identical commands in the history.
- if (self.history and self.history[-1] != self.input_buffer
- or not self.history):
- self.history.append(self.input_buffer)
-
- # Split the command up by spaces.
- line = self.input_buffer.split(b' ')
- self.logger.debug('cmd: %s', self.input_buffer)
- cmd = line[0].lower()
-
- # The 'history' command is a special case that we handle locally.
- if cmd == 'history':
- self.PrintHistory()
- return
-
- # Send the command to the interpreter.
- self.logger.debug('Sending command to interpreter.')
- self.cmd_pipe.send(self.input_buffer)
- def CheckForEnhancedECImage(self):
- """Performs an interrogation of the EC image.
+def CanonicalizeTimeString(timestr):
+ """Canonicalize the timestamp string.
- Send a SYN and expect an ACK. If no ACK or the response is incorrect, then
- assume that the current EC image that we are talking to is not enhanced.
+ Args:
+ timestr: A timestamp string ended with 6 digits msec.
Returns:
- is_enhanced: A boolean indicating whether the EC responded to the
- interrogation correctly.
-
- Raises:
- EOFError: Allowed to propagate through from self.dbg_pipe.recv().
+ A string with 3 digits msec and an extra space.
"""
- # Send interrogation byte and wait for the response.
- self.logger.debug('Performing interrogation.')
- self.cmd_pipe.send(interpreter.EC_SYN)
-
- response = ''
- if self.dbg_pipe.poll(self.interrogation_timeout):
- response = self.dbg_pipe.recv()
- self.logger.debug('response: %r', binascii.hexlify(response))
- else:
- self.logger.debug('Timed out waiting for EC_ACK')
+ return timestr[:-3].encode("ascii") + b" "
- # Verify the acknowledgment.
- is_enhanced = response == interpreter.EC_ACK
-
- if is_enhanced:
- # Increase the interrogation timeout for stability purposes.
- self.interrogation_timeout = ENHANCED_EC_INTERROGATION_TIMEOUT
- self.logger.debug('Increasing interrogation timeout to %rs.',
- self.interrogation_timeout)
- else:
- # Reduce the timeout in order to reduce the perceivable delay.
- self.interrogation_timeout = NON_ENHANCED_EC_INTERROGATION_TIMEOUT
- self.logger.debug('Reducing interrogation timeout to %rs.',
- self.interrogation_timeout)
-
- return is_enhanced
-
- def HandleChar(self, byte):
- """HandleChar does a certain action when it receives a character.
-
- Args:
- byte: An integer representing the character received from the user.
- Raises:
- EOFError: Allowed to propagate through from self.CheckForEnhancedECImage()
- i.e. from self.dbg_pipe.recv().
- """
- fd = self.controller_pty
-
- # Enter the OOBM prompt mode if the user presses '%'.
- if byte == ord('%'):
- self.logger.debug('Begin OOBM command.')
- self.receiving_oobm_cmd = True
- # Print a "prompt".
- os.write(self.controller_pty, b'\r\n% ')
- return
-
- # Add chars to the pending OOBM command if we're currently receiving one.
- if self.receiving_oobm_cmd and byte != ControlKey.CARRIAGE_RETURN:
- tmp_bytes = six.int2byte(byte)
- self.pending_oobm_cmd += tmp_bytes
- self.logger.debug('%s', tmp_bytes)
- os.write(self.controller_pty, tmp_bytes)
- return
-
- if byte == ControlKey.CARRIAGE_RETURN:
- if self.receiving_oobm_cmd:
- # Terminate the command and place it in the OOBM queue.
- self.logger.debug('End OOBM command.')
- if self.pending_oobm_cmd:
- self.oobm_queue.put(self.pending_oobm_cmd)
- self.logger.debug('Placed %r into OOBM command queue.',
- self.pending_oobm_cmd)
-
- # Reset the state.
- os.write(self.controller_pty, b'\r\n' + self.prompt)
- self.input_buffer = b''
- self.input_buffer_pos = 0
- self.receiving_oobm_cmd = False
- self.pending_oobm_cmd = b''
- return
-
- if self.interrogation_mode == b'never':
- self.logger.debug('Skipping interrogation because interrogation mode'
- ' is set to never.')
- elif self.interrogation_mode == b'always':
- # Only interrogate the EC if the interrogation mode is set to 'always'.
- self.enhanced_ec = self.CheckForEnhancedECImage()
- self.logger.debug('Enhanced EC image? %r', self.enhanced_ec)
-
- if not self.enhanced_ec:
- # Send everything straight to the EC to handle.
- self.cmd_pipe.send(six.int2byte(byte))
- # Reset the input buffer.
- self.input_buffer = b''
- self.input_buffer_pos = 0
- self.logger.log(1, 'Reset input buffer.')
- return
-
- # Keep handling the ESC sequence if we're in the middle of it.
- if self.esc_state != 0:
- self.HandleEsc(byte)
- return
-
- # When we're at the end of the line, we should only allow going backwards,
- # backspace, carriage return, up, or down. The arrow keys are escape
- # sequences, so we let the escape...escape.
- if (self.input_buffer_pos >= self.line_limit and
- byte not in [ControlKey.CTRL_B, ControlKey.ESC, ControlKey.BACKSPACE,
- ControlKey.CTRL_A, ControlKey.CARRIAGE_RETURN,
- ControlKey.CTRL_P, ControlKey.CTRL_N]):
- return
-
- # If the input buffer is full we can't accept new chars.
- buffer_full = len(self.input_buffer) >= self.line_limit
-
-
- # Carriage_Return/Enter
- if byte == ControlKey.CARRIAGE_RETURN:
- self.logger.debug('Enter key pressed.')
- # Put a carriage return/newline and the print the prompt.
- os.write(fd, b'\r\n')
-
- # TODO(aaboagye): When we control the printing of all output, print the
- # prompt AFTER printing all the output. We can't do it yet because we
- # don't know how much is coming from the EC.
-
- # Print the prompt.
- os.write(fd, self.prompt)
- # Process the input.
- self.ProcessInput()
- # Now, clear the buffer.
- self.input_buffer = b''
- self.input_buffer_pos = 0
- # Reset history buffer pos.
- self.history_pos = len(self.history)
- # Clear partial command.
- self.partial_cmd = b''
-
- # Backspace
- elif byte == ControlKey.BACKSPACE:
- self.logger.debug('Backspace pressed.')
- if self.input_buffer_pos > 0:
- # Move left 1 column.
- self.MoveCursor('left', 1)
- # Remove the character at the input_buffer_pos by slicing it out.
- self.SliceOutChar()
-
- self.logger.debug('input_buffer_pos: %d', self.input_buffer_pos)
-
- # Ctrl+A. Move cursor to beginning of the line
- elif byte == ControlKey.CTRL_A:
- self.logger.debug('Control+A pressed.')
- self.MoveCursor('left', self.input_buffer_pos)
-
- # Ctrl+B. Move cursor left 1 column.
- elif byte == ControlKey.CTRL_B:
- self.logger.debug('Control+B pressed.')
- self.MoveCursor('left', 1)
-
- # Ctrl+D. Delete a character.
- elif byte == ControlKey.CTRL_D:
- self.logger.debug('Control+D pressed.')
- if self.input_buffer_pos != len(self.input_buffer):
- # Remove the character by slicing it out.
- self.SliceOutChar()
-
- # Ctrl+E. Move cursor to end of the line.
- elif byte == ControlKey.CTRL_E:
- self.logger.debug('Control+E pressed.')
- self.MoveCursor('right',
- len(self.input_buffer) - self.input_buffer_pos)
-
- # Ctrl+F. Move cursor right 1 column.
- elif byte == ControlKey.CTRL_F:
- self.logger.debug('Control+F pressed.')
- self.MoveCursor('right', 1)
-
- # Ctrl+K. Kill line.
- elif byte == ControlKey.CTRL_K:
- self.logger.debug('Control+K pressed.')
- self.KillLine()
-
- # Ctrl+N. Next line.
- elif byte == ControlKey.CTRL_N:
- self.logger.debug('Control+N pressed.')
- self.ShowNextCommand()
-
- # Ctrl+P. Previous line.
- elif byte == ControlKey.CTRL_P:
- self.logger.debug('Control+P pressed.')
- self.ShowPreviousCommand()
-
- # ESC sequence
- elif byte == ControlKey.ESC:
- # Starting an ESC sequence
- self.esc_state = EscState.ESC_START
-
- # Only print printable chars.
- elif IsPrintable(byte):
- # Drop the character if we're full.
- if buffer_full:
- self.logger.debug('Dropped char: %c(%d)', byte, byte)
- return
- # Print the character.
- os.write(fd, six.int2byte(byte))
- # Print the rest of the line (if any).
- extra_bytes_written = os.write(fd,
- self.input_buffer[self.input_buffer_pos:])
-
- # Recreate the input buffer.
- self.input_buffer = (self.input_buffer[0:self.input_buffer_pos] +
- six.int2byte(byte) +
- self.input_buffer[self.input_buffer_pos:])
- # Update the input buffer position.
- self.input_buffer_pos += 1 + extra_bytes_written
-
- # Reset the cursor if we wrote any extra bytes.
- if extra_bytes_written:
- self.MoveCursor('left', extra_bytes_written)
-
- self.logger.debug('input_buffer_pos: %d', self.input_buffer_pos)
-
- def MoveCursor(self, direction, count):
- """MoveCursor moves the cursor left or right by count columns.
+def IsPrintable(byte):
+ """Determines if a byte is printable.
Args:
- direction: A string that should be either 'left' or 'right' representing
- the direction to move the cursor on the console.
- count: An integer representing how many columns the cursor should be
- moved.
+ byte: An integer potentially representing a printable character.
- Raises:
- AssertionError: If the direction is not equal to 'left' or 'right'.
+ Returns:
+ A boolean indicating whether the byte is a printable character.
"""
- # If there's nothing to move, we're done.
- if not count:
- return
- fd = self.controller_pty
- seq = b'\033[' + str(count).encode('ascii')
- if direction == 'left':
- # Bind the movement.
- if count > self.input_buffer_pos:
- count = self.input_buffer_pos
- seq += b'D'
- self.logger.debug('move cursor left %d', count)
- self.input_buffer_pos -= count
-
- elif direction == 'right':
- # Bind the movement.
- if (count + self.input_buffer_pos) > len(self.input_buffer):
- count = 0
- seq += b'C'
- self.logger.debug('move cursor right %d', count)
- self.input_buffer_pos += count
-
- else:
- raise AssertionError(('The only valid directions are \'left\' and '
- '\'right\''))
-
- self.logger.debug('input_buffer_pos: %d', self.input_buffer_pos)
- # Move the cursor.
- if count != 0:
- os.write(fd, seq)
-
- def KillLine(self):
- """Kill the rest of the line based on the input buffer position."""
- # Killing the line is killing all the text to the right.
- diff = len(self.input_buffer) - self.input_buffer_pos
- self.logger.debug('diff: %d', diff)
- # Diff shouldn't be negative, but if it is for some reason, let's try to
- # correct the cursor.
- if diff < 0:
- self.logger.warning('Resetting input buffer position to %d...',
- len(self.input_buffer))
- self.MoveCursor('left', -diff)
- return
- if diff:
- self.MoveCursor('right', diff)
- for _ in range(diff):
- self.SendBackspace()
- self.input_buffer_pos -= diff
- self.input_buffer = self.input_buffer[0:self.input_buffer_pos]
-
- def SendBackspace(self):
- """Backspace a character on the console."""
- os.write(self.controller_pty, b'\033[1D \033[1D')
-
- def ProcessOOBMQueue(self):
- """Retrieve an item from the OOBM queue and process it."""
- item = self.oobm_queue.get()
- self.logger.debug('OOBM cmd: %r', item)
- cmd = item.split(b' ')
-
- if cmd[0] == b'loglevel':
- # An integer is required in order to set the log level.
- if len(cmd) < 2:
- self.logger.debug('Insufficient args')
- self.PrintOOBMHelp()
- return
- try:
- self.logger.debug('Log level change request.')
- new_log_level = int(cmd[1])
- self.logger.logger.setLevel(new_log_level)
- self.logger.info('Log level changed to %d.', new_log_level)
-
- # Forward the request to the interpreter as well.
- self.cmd_pipe.send(item)
- except ValueError:
- # Ignoring the request if an integer was not provided.
- self.PrintOOBMHelp()
-
- elif cmd[0] == b'timestamp':
- mode = cmd[1].lower()
- self.timestamp_enabled = (mode == b'on')
- self.logger.info('%sabling uart timestamps.',
- 'En' if self.timestamp_enabled else 'Dis')
-
- elif cmd[0] == b'rawdebug':
- mode = cmd[1].lower()
- self.raw_debug = (mode == b'on')
- self.logger.info('%sabling per interrupt debug logs.',
- 'En' if self.raw_debug else 'Dis')
-
- elif cmd[0] == b'interrogate' and len(cmd) >= 2:
- enhanced = False
- mode = cmd[1]
- if len(cmd) >= 3 and cmd[2] == b'enhanced':
- enhanced = True
-
- # Set the mode if correct.
- if mode in INTERROGATION_MODES:
- self.interrogation_mode = mode
- self.logger.debug('Updated interrogation mode to %s.', mode)
-
- # Update the assumptions of the EC image.
- self.enhanced_ec = enhanced
- self.logger.debug('Enhanced EC image is now %r', self.enhanced_ec)
-
- # Send command to interpreter as well.
- self.cmd_pipe.send(b'enhanced ' + str(self.enhanced_ec).encode('ascii'))
- else:
- self.PrintOOBMHelp()
-
- else:
- self.PrintOOBMHelp()
+ return byte >= ord(" ") and byte <= ord("~")
- def PrintOOBMHelp(self):
- """Prints out the OOBM help."""
- # Print help syntax.
- os.write(self.controller_pty, b'\r\n' + b'Known OOBM commands:\r\n')
- os.write(self.controller_pty, b' interrogate <never | always | auto> '
- b'[enhanced]\r\n')
- os.write(self.controller_pty, b' loglevel <int>\r\n')
- def CheckBufferForEnhancedImage(self, data):
- """Adds data to a look buffer and checks to see for enhanced EC image.
-
- The EC's console task prints a string upon initialization which says that
- "Console is enabled; type HELP for help.". The enhanced EC images print a
- different string as a part of their init. This function searches through a
- "look" buffer, scanning for the presence of either of those strings and
- updating the enhanced_ec state accordingly.
+def StartLoop(console, command_active, shutdown_pipe=None):
+ """Starts the infinite loop of console processing.
Args:
- data: A string containing the data sent from the interpreter.
+ console: A Console object that has been properly initialzed.
+ command_active: ctypes data object or multiprocessing.Value indicating if
+ servod owns the console, or user owns the console. This prevents input
+ collisions.
+ shutdown_pipe: A file object for a pipe or equivalent that becomes readable
+ (not blocked) to indicate that the loop should exit. Can be None to never
+ exit the loop.
"""
- self.look_buffer += data
-
- # Search the buffer for any of the EC image strings.
- enhanced_match = re.search(ENHANCED_IMAGE_RE, self.look_buffer)
- non_enhanced_match = re.search(NON_ENHANCED_IMAGE_RE, self.look_buffer)
-
- # Update the state if any matches were found.
- if enhanced_match or non_enhanced_match:
- if enhanced_match:
- self.enhanced_ec = True
- elif non_enhanced_match:
- self.enhanced_ec = False
-
- # Inform the interpreter of the result.
- self.cmd_pipe.send(b'enhanced ' + str(self.enhanced_ec).encode('ascii'))
- self.logger.debug('Enhanced EC image? %r', self.enhanced_ec)
-
- # Clear look buffer since a match was found.
- self.look_buffer = b''
-
- # Move the sliding window.
- self.look_buffer = self.look_buffer[-LOOK_BUFFER_SIZE:]
-
-
-def CanonicalizeTimeString(timestr):
- """Canonicalize the timestamp string.
-
- Args:
- timestr: A timestamp string ended with 6 digits msec.
-
- Returns:
- A string with 3 digits msec and an extra space.
- """
- return timestr[:-3].encode('ascii') + b' '
-
-
-def IsPrintable(byte):
- """Determines if a byte is printable.
-
- Args:
- byte: An integer potentially representing a printable character.
-
- Returns:
- A boolean indicating whether the byte is a printable character.
- """
- return byte >= ord(' ') and byte <= ord('~')
-
-
-def StartLoop(console, command_active, shutdown_pipe=None):
- """Starts the infinite loop of console processing.
-
- Args:
- console: A Console object that has been properly initialzed.
- command_active: ctypes data object or multiprocessing.Value indicating if
- servod owns the console, or user owns the console. This prevents input
- collisions.
- shutdown_pipe: A file object for a pipe or equivalent that becomes readable
- (not blocked) to indicate that the loop should exit. Can be None to never
- exit the loop.
- """
- try:
- console.logger.debug('Console is being served on %s.', console.user_pty)
- console.logger.debug('Console controller is on %s.', console.controller_pty)
- console.logger.debug('Command interface is being served on %s.',
- console.interface_pty)
- console.logger.debug(console)
-
- # This checks for HUP to indicate if the user has connected to the pty.
- ep = select.epoll()
- ep.register(console.controller_pty, select.EPOLLHUP)
-
- # This is used instead of "break" to avoid exiting the loop in the middle of
- # an iteration.
- continue_looping = True
-
- # Used for determining when to print host timestamps
- tm_req = True
-
- while continue_looping:
- # Check to see if pts is connected to anything
- events = ep.poll(0)
- controller_connected = not events
-
- # Check to see if pipes or the console are ready for reading.
- read_list = [console.interface_pty,
- console.cmd_pipe, console.dbg_pipe]
- if controller_connected:
- read_list.append(console.controller_pty)
- if shutdown_pipe is not None:
- read_list.append(shutdown_pipe)
-
- # Check if any input is ready, or wait for .1 sec and re-poll if
- # a user has connected to the pts.
- select_output = select.select(read_list, [], [], .1)
- if not select_output:
- continue
- ready_for_reading = select_output[0]
-
- for obj in ready_for_reading:
- if obj is console.controller_pty:
- if not command_active.value:
- # Convert to bytes so we can look for non-printable chars such as
- # Ctrl+A, Ctrl+E, etc.
- try:
- line = bytearray(os.read(console.controller_pty, CONSOLE_MAX_READ))
- console.logger.debug('Input from user: %s, locked:%s',
- str(line).strip(), command_active.value)
- for i in line:
- try:
- # Handle each character as it arrives.
- console.HandleChar(i)
- except EOFError:
- console.logger.debug(
- 'ec3po console received EOF from dbg_pipe in HandleChar()'
- ' while reading console.controller_pty')
- continue_looping = False
- break
- except OSError:
- console.logger.debug('Ptm read failed, probably user disconnect.')
-
- elif obj is console.interface_pty:
- if command_active.value:
- # Convert to bytes so we can look for non-printable chars such as
- # Ctrl+A, Ctrl+E, etc.
- line = bytearray(os.read(console.interface_pty, CONSOLE_MAX_READ))
- console.logger.debug('Input from interface: %s, locked:%s',
- str(line).strip(), command_active.value)
- for i in line:
- try:
- # Handle each character as it arrives.
- console.HandleChar(i)
- except EOFError:
- console.logger.debug(
- 'ec3po console received EOF from dbg_pipe in HandleChar()'
- ' while reading console.interface_pty')
- continue_looping = False
- break
-
- elif obj is console.cmd_pipe:
- try:
- data = console.cmd_pipe.recv()
- except EOFError:
- console.logger.debug('ec3po console received EOF from cmd_pipe')
- continue_looping = False
- else:
- # Write it to the user console.
- if console.raw_debug:
- console.logger.debug('|CMD|-%s->%r',
- ('u' if controller_connected else '') +
- ('i' if command_active.value else ''),
- data.strip())
+ try:
+ console.logger.debug("Console is being served on %s.", console.user_pty)
+ console.logger.debug("Console controller is on %s.", console.controller_pty)
+ console.logger.debug(
+ "Command interface is being served on %s.", console.interface_pty
+ )
+ console.logger.debug(console)
+
+ # This checks for HUP to indicate if the user has connected to the pty.
+ ep = select.epoll()
+ ep.register(console.controller_pty, select.EPOLLHUP)
+
+ # This is used instead of "break" to avoid exiting the loop in the middle of
+ # an iteration.
+ continue_looping = True
+
+ # Used for determining when to print host timestamps
+ tm_req = True
+
+ while continue_looping:
+ # Check to see if pts is connected to anything
+ events = ep.poll(0)
+ controller_connected = not events
+
+ # Check to see if pipes or the console are ready for reading.
+ read_list = [console.interface_pty, console.cmd_pipe, console.dbg_pipe]
if controller_connected:
- os.write(console.controller_pty, data)
- if command_active.value:
- os.write(console.interface_pty, data)
-
- elif obj is console.dbg_pipe:
- try:
- data = console.dbg_pipe.recv()
- except EOFError:
- console.logger.debug('ec3po console received EOF from dbg_pipe')
- continue_looping = False
- else:
- if console.interrogation_mode == b'auto':
- # Search look buffer for enhanced EC image string.
- console.CheckBufferForEnhancedImage(data)
- # Write it to the user console.
- if len(data) > 1 and console.raw_debug:
- console.logger.debug('|DBG|-%s->%r',
- ('u' if controller_connected else '') +
- ('i' if command_active.value else ''),
- data.strip())
- console.LogConsoleOutput(data)
- if controller_connected:
- end = len(data) - 1
- if console.timestamp_enabled:
- # A timestamp is required at the beginning of this line
- if tm_req is True:
- now = datetime.now()
- tm = CanonicalizeTimeString(now.strftime(HOST_STRFTIME))
- os.write(console.controller_pty, tm)
- tm_req = False
-
- # Insert timestamps into the middle where appropriate
- # except if the last character is a newline
- nls_found = data.count(b'\n', 0, end)
- now = datetime.now()
- tm = CanonicalizeTimeString(now.strftime('\n' + HOST_STRFTIME))
- data_tm = data.replace(b'\n', tm, nls_found)
- else:
- data_tm = data
-
- # timestamp required on next input
- if data[end] == b'\n'[0]:
- tm_req = True
- os.write(console.controller_pty, data_tm)
- if command_active.value:
- os.write(console.interface_pty, data)
-
- elif obj is shutdown_pipe:
- console.logger.debug(
- 'ec3po console received shutdown pipe unblocked notification')
- continue_looping = False
-
- while not console.oobm_queue.empty():
- console.logger.debug('OOBM queue ready for reading.')
- console.ProcessOOBMQueue()
-
- except KeyboardInterrupt:
- pass
-
- finally:
- ep.unregister(console.controller_pty)
- console.dbg_pipe.close()
- console.cmd_pipe.close()
- os.close(console.controller_pty)
- os.close(console.interface_pty)
- if shutdown_pipe is not None:
- shutdown_pipe.close()
- console.logger.debug('Exit ec3po console loop for %s', console.user_pty)
+ read_list.append(console.controller_pty)
+ if shutdown_pipe is not None:
+ read_list.append(shutdown_pipe)
+
+ # Check if any input is ready, or wait for .1 sec and re-poll if
+ # a user has connected to the pts.
+ select_output = select.select(read_list, [], [], 0.1)
+ if not select_output:
+ continue
+ ready_for_reading = select_output[0]
+
+ for obj in ready_for_reading:
+ if obj is console.controller_pty:
+ if not command_active.value:
+ # Convert to bytes so we can look for non-printable chars such as
+ # Ctrl+A, Ctrl+E, etc.
+ try:
+ line = bytearray(
+ os.read(console.controller_pty, CONSOLE_MAX_READ)
+ )
+ console.logger.debug(
+ "Input from user: %s, locked:%s",
+ str(line).strip(),
+ command_active.value,
+ )
+ for i in line:
+ try:
+ # Handle each character as it arrives.
+ console.HandleChar(i)
+ except EOFError:
+ console.logger.debug(
+ "ec3po console received EOF from dbg_pipe in HandleChar()"
+ " while reading console.controller_pty"
+ )
+ continue_looping = False
+ break
+ except OSError:
+ console.logger.debug(
+ "Ptm read failed, probably user disconnect."
+ )
+
+ elif obj is console.interface_pty:
+ if command_active.value:
+ # Convert to bytes so we can look for non-printable chars such as
+ # Ctrl+A, Ctrl+E, etc.
+ line = bytearray(
+ os.read(console.interface_pty, CONSOLE_MAX_READ)
+ )
+ console.logger.debug(
+ "Input from interface: %s, locked:%s",
+ str(line).strip(),
+ command_active.value,
+ )
+ for i in line:
+ try:
+ # Handle each character as it arrives.
+ console.HandleChar(i)
+ except EOFError:
+ console.logger.debug(
+ "ec3po console received EOF from dbg_pipe in HandleChar()"
+ " while reading console.interface_pty"
+ )
+ continue_looping = False
+ break
+
+ elif obj is console.cmd_pipe:
+ try:
+ data = console.cmd_pipe.recv()
+ except EOFError:
+ console.logger.debug("ec3po console received EOF from cmd_pipe")
+ continue_looping = False
+ else:
+ # Write it to the user console.
+ if console.raw_debug:
+ console.logger.debug(
+ "|CMD|-%s->%r",
+ ("u" if controller_connected else "")
+ + ("i" if command_active.value else ""),
+ data.strip(),
+ )
+ if controller_connected:
+ os.write(console.controller_pty, data)
+ if command_active.value:
+ os.write(console.interface_pty, data)
+
+ elif obj is console.dbg_pipe:
+ try:
+ data = console.dbg_pipe.recv()
+ except EOFError:
+ console.logger.debug("ec3po console received EOF from dbg_pipe")
+ continue_looping = False
+ else:
+ if console.interrogation_mode == b"auto":
+ # Search look buffer for enhanced EC image string.
+ console.CheckBufferForEnhancedImage(data)
+ # Write it to the user console.
+ if len(data) > 1 and console.raw_debug:
+ console.logger.debug(
+ "|DBG|-%s->%r",
+ ("u" if controller_connected else "")
+ + ("i" if command_active.value else ""),
+ data.strip(),
+ )
+ console.LogConsoleOutput(data)
+ if controller_connected:
+ end = len(data) - 1
+ if console.timestamp_enabled:
+ # A timestamp is required at the beginning of this line
+ if tm_req is True:
+ now = datetime.now()
+ tm = CanonicalizeTimeString(
+ now.strftime(HOST_STRFTIME)
+ )
+ os.write(console.controller_pty, tm)
+ tm_req = False
+
+ # Insert timestamps into the middle where appropriate
+ # except if the last character is a newline
+ nls_found = data.count(b"\n", 0, end)
+ now = datetime.now()
+ tm = CanonicalizeTimeString(
+ now.strftime("\n" + HOST_STRFTIME)
+ )
+ data_tm = data.replace(b"\n", tm, nls_found)
+ else:
+ data_tm = data
+
+ # timestamp required on next input
+ if data[end] == b"\n"[0]:
+ tm_req = True
+ os.write(console.controller_pty, data_tm)
+ if command_active.value:
+ os.write(console.interface_pty, data)
+
+ elif obj is shutdown_pipe:
+ console.logger.debug(
+ "ec3po console received shutdown pipe unblocked notification"
+ )
+ continue_looping = False
+
+ while not console.oobm_queue.empty():
+ console.logger.debug("OOBM queue ready for reading.")
+ console.ProcessOOBMQueue()
+
+ except KeyboardInterrupt:
+ pass
+
+ finally:
+ ep.unregister(console.controller_pty)
+ console.dbg_pipe.close()
+ console.cmd_pipe.close()
+ os.close(console.controller_pty)
+ os.close(console.interface_pty)
+ if shutdown_pipe is not None:
+ shutdown_pipe.close()
+ console.logger.debug("Exit ec3po console loop for %s", console.user_pty)
def main(argv):
- """Kicks off the EC-3PO interactive console interface and interpreter.
-
- We create some pipes to communicate with an interpreter, instantiate an
- interpreter, create a PTY pair, and begin serving the console interface.
-
- Args:
- argv: A list of strings containing the arguments this module was called
- with.
- """
- # Set up argument parser.
- parser = argparse.ArgumentParser(description=('Start interactive EC console '
- 'and interpreter.'))
- parser.add_argument('ec_uart_pty',
- help=('The full PTY name that the EC UART'
- ' is present on. eg: /dev/pts/12'))
- parser.add_argument('--log-level',
- default='info',
- help='info, debug, warning, error, or critical')
-
- # Parse arguments.
- opts = parser.parse_args(argv)
-
- # Set logging level.
- opts.log_level = opts.log_level.lower()
- if opts.log_level == 'info':
- log_level = logging.INFO
- elif opts.log_level == 'debug':
- log_level = logging.DEBUG
- elif opts.log_level == 'warning':
- log_level = logging.WARNING
- elif opts.log_level == 'error':
- log_level = logging.ERROR
- elif opts.log_level == 'critical':
- log_level = logging.CRITICAL
- else:
- parser.error('Invalid log level. (info, debug, warning, error, critical)')
-
- # Start logging with a timestamp, module, and log level shown in each log
- # entry.
- logging.basicConfig(level=log_level, format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
-
- # Create some pipes to communicate between the interpreter and the console.
- # The command pipe is bidirectional.
- cmd_pipe_interactive, cmd_pipe_interp = threadproc_shim.Pipe()
- # The debug pipe is unidirectional from interpreter to console only.
- dbg_pipe_interactive, dbg_pipe_interp = threadproc_shim.Pipe(duplex=False)
-
- # Create an interpreter instance.
- itpr = interpreter.Interpreter(opts.ec_uart_pty, cmd_pipe_interp,
- dbg_pipe_interp, log_level)
-
- # Spawn an interpreter process.
- itpr_process = threadproc_shim.ThreadOrProcess(
- target=interpreter.StartLoop, args=(itpr,))
- # Make sure to kill the interpreter when we terminate.
- itpr_process.daemon = True
- # Start the interpreter.
- itpr_process.start()
-
- # Open a new pseudo-terminal pair
- (controller_pty, user_pty) = pty.openpty()
- # Set the permissions to 660.
- os.chmod(os.ttyname(user_pty), (stat.S_IRGRP | stat.S_IWGRP |
- stat.S_IRUSR | stat.S_IWUSR))
- # Create a console.
- console = Console(controller_pty, os.ttyname(user_pty), cmd_pipe_interactive,
- dbg_pipe_interactive)
- # Start serving the console.
- v = threadproc_shim.Value(ctypes.c_bool, False)
- StartLoop(console, v)
-
-
-if __name__ == '__main__':
- main(sys.argv[1:])
+ """Kicks off the EC-3PO interactive console interface and interpreter.
+
+ We create some pipes to communicate with an interpreter, instantiate an
+ interpreter, create a PTY pair, and begin serving the console interface.
+
+ Args:
+ argv: A list of strings containing the arguments this module was called
+ with.
+ """
+ # Set up argument parser.
+ parser = argparse.ArgumentParser(
+ description=("Start interactive EC console " "and interpreter.")
+ )
+ parser.add_argument(
+ "ec_uart_pty",
+ help=("The full PTY name that the EC UART" " is present on. eg: /dev/pts/12"),
+ )
+ parser.add_argument(
+ "--log-level", default="info", help="info, debug, warning, error, or critical"
+ )
+
+ # Parse arguments.
+ opts = parser.parse_args(argv)
+
+ # Set logging level.
+ opts.log_level = opts.log_level.lower()
+ if opts.log_level == "info":
+ log_level = logging.INFO
+ elif opts.log_level == "debug":
+ log_level = logging.DEBUG
+ elif opts.log_level == "warning":
+ log_level = logging.WARNING
+ elif opts.log_level == "error":
+ log_level = logging.ERROR
+ elif opts.log_level == "critical":
+ log_level = logging.CRITICAL
+ else:
+ parser.error("Invalid log level. (info, debug, warning, error, critical)")
+
+ # Start logging with a timestamp, module, and log level shown in each log
+ # entry.
+ logging.basicConfig(
+ level=log_level,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+
+ # Create some pipes to communicate between the interpreter and the console.
+ # The command pipe is bidirectional.
+ cmd_pipe_interactive, cmd_pipe_interp = threadproc_shim.Pipe()
+ # The debug pipe is unidirectional from interpreter to console only.
+ dbg_pipe_interactive, dbg_pipe_interp = threadproc_shim.Pipe(duplex=False)
+
+ # Create an interpreter instance.
+ itpr = interpreter.Interpreter(
+ opts.ec_uart_pty, cmd_pipe_interp, dbg_pipe_interp, log_level
+ )
+
+ # Spawn an interpreter process.
+ itpr_process = threadproc_shim.ThreadOrProcess(
+ target=interpreter.StartLoop, args=(itpr,)
+ )
+ # Make sure to kill the interpreter when we terminate.
+ itpr_process.daemon = True
+ # Start the interpreter.
+ itpr_process.start()
+
+ # Open a new pseudo-terminal pair
+ (controller_pty, user_pty) = pty.openpty()
+ # Set the permissions to 660.
+ os.chmod(
+ os.ttyname(user_pty),
+ (stat.S_IRGRP | stat.S_IWGRP | stat.S_IRUSR | stat.S_IWUSR),
+ )
+ # Create a console.
+ console = Console(
+ controller_pty, os.ttyname(user_pty), cmd_pipe_interactive, dbg_pipe_interactive
+ )
+ # Start serving the console.
+ v = threadproc_shim.Value(ctypes.c_bool, False)
+ StartLoop(console, v)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/util/ec3po/console_unittest.py b/util/ec3po/console_unittest.py
index 7e341e7e8d..41ae324ef4 100755
--- a/util/ec3po/console_unittest.py
+++ b/util/ec3po/console_unittest.py
@@ -11,1262 +11,1310 @@ from __future__ import print_function
import binascii
import logging
-import mock
import tempfile
import unittest
+import mock
import six
-
-from ec3po import console
-from ec3po import interpreter
-from ec3po import threadproc_shim
+from ec3po import console, interpreter, threadproc_shim
ESC_STRING = six.int2byte(console.ControlKey.ESC)
+
class Keys(object):
- """A class that contains the escape sequences for special keys."""
- LEFT_ARROW = [console.ControlKey.ESC, ord('['), ord('D')]
- RIGHT_ARROW = [console.ControlKey.ESC, ord('['), ord('C')]
- UP_ARROW = [console.ControlKey.ESC, ord('['), ord('A')]
- DOWN_ARROW = [console.ControlKey.ESC, ord('['), ord('B')]
- HOME = [console.ControlKey.ESC, ord('['), ord('1'), ord('~')]
- END = [console.ControlKey.ESC, ord('['), ord('8'), ord('~')]
- DEL = [console.ControlKey.ESC, ord('['), ord('3'), ord('~')]
+ """A class that contains the escape sequences for special keys."""
+
+ LEFT_ARROW = [console.ControlKey.ESC, ord("["), ord("D")]
+ RIGHT_ARROW = [console.ControlKey.ESC, ord("["), ord("C")]
+ UP_ARROW = [console.ControlKey.ESC, ord("["), ord("A")]
+ DOWN_ARROW = [console.ControlKey.ESC, ord("["), ord("B")]
+ HOME = [console.ControlKey.ESC, ord("["), ord("1"), ord("~")]
+ END = [console.ControlKey.ESC, ord("["), ord("8"), ord("~")]
+ DEL = [console.ControlKey.ESC, ord("["), ord("3"), ord("~")]
+
class OutputStream(object):
- """A class that has methods which return common console output."""
+ """A class that has methods which return common console output."""
- @staticmethod
- def MoveCursorLeft(count):
- """Produces what would be printed to the console if the cursor moved left.
+ @staticmethod
+ def MoveCursorLeft(count):
+ """Produces what would be printed to the console if the cursor moved left.
- Args:
- count: An integer representing how many columns to move left.
+ Args:
+ count: An integer representing how many columns to move left.
- Returns:
- string: A string which contains what would be printed to the console if
- the cursor moved left.
- """
- string = ESC_STRING
- string += b'[' + str(count).encode('ascii') + b'D'
- return string
+ Returns:
+ string: A string which contains what would be printed to the console if
+ the cursor moved left.
+ """
+ string = ESC_STRING
+ string += b"[" + str(count).encode("ascii") + b"D"
+ return string
- @staticmethod
- def MoveCursorRight(count):
- """Produces what would be printed to the console if the cursor moved right.
+ @staticmethod
+ def MoveCursorRight(count):
+ """Produces what would be printed to the console if the cursor moved right.
- Args:
- count: An integer representing how many columns to move right.
+ Args:
+ count: An integer representing how many columns to move right.
- Returns:
- string: A string which contains what would be printed to the console if
- the cursor moved right.
- """
- string = ESC_STRING
- string += b'[' + str(count).encode('ascii') + b'C'
- return string
+ Returns:
+ string: A string which contains what would be printed to the console if
+ the cursor moved right.
+ """
+ string = ESC_STRING
+ string += b"[" + str(count).encode("ascii") + b"C"
+ return string
-BACKSPACE_STRING = b''
+
+BACKSPACE_STRING = b""
# Move cursor left 1 column.
BACKSPACE_STRING += OutputStream.MoveCursorLeft(1)
# Write a space.
-BACKSPACE_STRING += b' '
+BACKSPACE_STRING += b" "
# Move cursor left 1 column.
BACKSPACE_STRING += OutputStream.MoveCursorLeft(1)
+
def BytesToByteList(string):
- """Converts a bytes string to list of bytes.
+ """Converts a bytes string to list of bytes.
+
+ Args:
+ string: A literal bytes to turn into a list of bytes.
- Args:
- string: A literal bytes to turn into a list of bytes.
+ Returns:
+ A list of integers representing the byte value of each character in the
+ string.
+ """
+ if six.PY3:
+ return [c for c in string]
+ return [ord(c) for c in string]
- Returns:
- A list of integers representing the byte value of each character in the
- string.
- """
- if six.PY3:
- return [c for c in string]
- return [ord(c) for c in string]
def CheckConsoleOutput(test_case, exp_console_out):
- """Verify what was sent out the console matches what we expect.
+ """Verify what was sent out the console matches what we expect.
- Args:
- test_case: A unittest.TestCase object representing the current unit test.
- exp_console_out: A string representing the console output stream.
- """
- # Read what was sent out the console.
- test_case.tempfile.seek(0)
- console_out = test_case.tempfile.read()
+ Args:
+ test_case: A unittest.TestCase object representing the current unit test.
+ exp_console_out: A string representing the console output stream.
+ """
+ # Read what was sent out the console.
+ test_case.tempfile.seek(0)
+ console_out = test_case.tempfile.read()
- test_case.assertEqual(exp_console_out, console_out)
+ test_case.assertEqual(exp_console_out, console_out)
-def CheckInputBuffer(test_case, exp_input_buffer):
- """Verify that the input buffer contains what we expect.
-
- Args:
- test_case: A unittest.TestCase object representing the current unit test.
- exp_input_buffer: A string containing the contents of the current input
- buffer.
- """
- test_case.assertEqual(exp_input_buffer, test_case.console.input_buffer,
- (b'input buffer does not match expected.\n'
- b'expected: |' + exp_input_buffer + b'|\n'
- b'got: |' + test_case.console.input_buffer +
- b'|\n' + str(test_case.console).encode('ascii')))
-def CheckInputBufferPosition(test_case, exp_pos):
- """Verify the input buffer position.
+def CheckInputBuffer(test_case, exp_input_buffer):
+ """Verify that the input buffer contains what we expect.
- Args:
- test_case: A unittest.TestCase object representing the current unit test.
- exp_pos: An integer representing the expected input buffer position.
- """
- test_case.assertEqual(exp_pos, test_case.console.input_buffer_pos,
- 'input buffer position is incorrect.\ngot: ' +
- str(test_case.console.input_buffer_pos) + '\nexp: ' +
- str(exp_pos) + '\n' + str(test_case.console))
+ Args:
+ test_case: A unittest.TestCase object representing the current unit test.
+ exp_input_buffer: A string containing the contents of the current input
+ buffer.
+ """
+ test_case.assertEqual(
+ exp_input_buffer,
+ test_case.console.input_buffer,
+ (
+ b"input buffer does not match expected.\n"
+ b"expected: |" + exp_input_buffer + b"|\n"
+ b"got: |"
+ + test_case.console.input_buffer
+ + b"|\n"
+ + str(test_case.console).encode("ascii")
+ ),
+ )
-def CheckHistoryBuffer(test_case, exp_history):
- """Verify that the items in the history buffer are what we expect.
-
- Args:
- test_case: A unittest.TestCase object representing the current unit test.
- exp_history: A list of strings representing the expected contents of the
- history buffer.
- """
- # First, check to see if the length is what we expect.
- test_case.assertEqual(len(exp_history), len(test_case.console.history),
- ('The number of items in the history is unexpected.\n'
- 'exp: ' + str(len(exp_history)) + '\n'
- 'got: ' + str(len(test_case.console.history)) + '\n'
- 'internal state:\n' + str(test_case.console)))
-
- # Next, check the actual contents of the history buffer.
- for i in range(len(exp_history)):
- test_case.assertEqual(exp_history[i], test_case.console.history[i],
- (b'history buffer contents are incorrect.\n'
- b'exp: ' + exp_history[i] + b'\n'
- b'got: ' + test_case.console.history[i] + b'\n'
- b'internal state:\n' +
- str(test_case.console).encode('ascii')))
+def CheckInputBufferPosition(test_case, exp_pos):
+ """Verify the input buffer position.
-class TestConsoleEditingMethods(unittest.TestCase):
- """Test case to verify all console editing methods."""
-
- def setUp(self):
- """Setup the test harness."""
- # Setup logging with a timestamp, the module, and the log level.
- logging.basicConfig(level=logging.DEBUG,
- format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
-
- # Create a temp file and set both the controller and peripheral PTYs to the
- # file to create a loopback.
- self.tempfile = tempfile.TemporaryFile()
-
- # Create some mock pipes. These won't be used since we'll mock out sends
- # to the interpreter.
- mock_pipe_end_0, mock_pipe_end_1 = threadproc_shim.Pipe()
- self.console = console.Console(self.tempfile.fileno(), self.tempfile,
- tempfile.TemporaryFile(),
- mock_pipe_end_0, mock_pipe_end_1, "EC")
-
- # Console editing methods are only valid for enhanced EC images, therefore
- # we have to assume that the "EC" we're talking to is enhanced. By default,
- # the console believes that the EC it's communicating with is NOT enhanced
- # which is why we have to override it here.
- self.console.enhanced_ec = True
- self.console.CheckForEnhancedECImage = mock.MagicMock(return_value=True)
-
- def test_EnteringChars(self):
- """Verify that characters are echoed onto the console."""
- test_str = b'abc'
- input_stream = BytesToByteList(test_str)
-
- # Send the characters in.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Check the input position.
- exp_pos = len(test_str)
- CheckInputBufferPosition(self, exp_pos)
-
- # Verify that the input buffer is correct.
- expected_buffer = test_str
- CheckInputBuffer(self, expected_buffer)
-
- # Check console output
- exp_console_out = test_str
- CheckConsoleOutput(self, exp_console_out)
-
- def test_EnteringDeletingMoreCharsThanEntered(self):
- """Verify that we can press backspace more than we have entered chars."""
- test_str = b'spamspam'
- input_stream = BytesToByteList(test_str)
-
- # Send the characters in.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Now backspace 1 more than what we sent.
- input_stream = []
- for _ in range(len(test_str) + 1):
- input_stream.append(console.ControlKey.BACKSPACE)
-
- # Send that sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, verify that input buffer position is 0.
- CheckInputBufferPosition(self, 0)
-
- # Next, examine the output stream for the correct sequence.
- exp_console_out = test_str
- for _ in range(len(test_str)):
- exp_console_out += BACKSPACE_STRING
-
- # Now, verify that we got what we expected.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_EnteringMoreThanCharLimit(self):
- """Verify that we drop characters when the line is too long."""
- test_str = self.console.line_limit * b'o' # All allowed.
- test_str += 5 * b'x' # All should be dropped.
- input_stream = BytesToByteList(test_str)
-
- # Send the characters in.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, we expect that input buffer position should be equal to the line
- # limit.
- exp_pos = self.console.line_limit
- CheckInputBufferPosition(self, exp_pos)
-
- # The input buffer should only hold until the line limit.
- exp_buffer = test_str[0:self.console.line_limit]
- CheckInputBuffer(self, exp_buffer)
-
- # Lastly, check that the extra characters are not printed.
- exp_console_out = exp_buffer
- CheckConsoleOutput(self, exp_console_out)
-
- def test_ValidKeysOnLongLine(self):
- """Verify that we can still press valid keys if the line is too long."""
- # Fill the line.
- test_str = self.console.line_limit * b'o'
- exp_console_out = test_str
- # Try to fill it even more; these should all be dropped.
- test_str += 5 * b'x'
- input_stream = BytesToByteList(test_str)
-
- # We should be able to press the following keys:
- # - Backspace
- # - Arrow Keys/CTRL+B/CTRL+F/CTRL+P/CTRL+N
- # - Delete
- # - Home/CTRL+A
- # - End/CTRL+E
- # - Carriage Return
-
- # Backspace 1 character
- input_stream.append(console.ControlKey.BACKSPACE)
- exp_console_out += BACKSPACE_STRING
- # Refill the line.
- input_stream.extend(BytesToByteList(b'o'))
- exp_console_out += b'o'
-
- # Left arrow key.
- input_stream.extend(Keys.LEFT_ARROW)
- exp_console_out += OutputStream.MoveCursorLeft(1)
-
- # Right arrow key.
- input_stream.extend(Keys.RIGHT_ARROW)
- exp_console_out += OutputStream.MoveCursorRight(1)
-
- # CTRL+B
- input_stream.append(console.ControlKey.CTRL_B)
- exp_console_out += OutputStream.MoveCursorLeft(1)
-
- # CTRL+F
- input_stream.append(console.ControlKey.CTRL_F)
- exp_console_out += OutputStream.MoveCursorRight(1)
-
- # Let's press enter now so we can test up and down.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- exp_console_out += b'\r\n' + self.console.prompt
-
- # Up arrow key.
- input_stream.extend(Keys.UP_ARROW)
- exp_console_out += test_str[:self.console.line_limit]
-
- # Down arrow key.
- input_stream.extend(Keys.DOWN_ARROW)
- # Since the line was blank, we have to backspace the entire line.
- exp_console_out += self.console.line_limit * BACKSPACE_STRING
-
- # CTRL+P
- input_stream.append(console.ControlKey.CTRL_P)
- exp_console_out += test_str[:self.console.line_limit]
-
- # CTRL+N
- input_stream.append(console.ControlKey.CTRL_N)
- # Since the line was blank, we have to backspace the entire line.
- exp_console_out += self.console.line_limit * BACKSPACE_STRING
-
- # Press the Up arrow key to reprint the long line.
- input_stream.extend(Keys.UP_ARROW)
- exp_console_out += test_str[:self.console.line_limit]
-
- # Press the Home key to jump to the beginning of the line.
- input_stream.extend(Keys.HOME)
- exp_console_out += OutputStream.MoveCursorLeft(self.console.line_limit)
-
- # Press the End key to jump to the end of the line.
- input_stream.extend(Keys.END)
- exp_console_out += OutputStream.MoveCursorRight(self.console.line_limit)
-
- # Press CTRL+A to jump to the beginning of the line.
- input_stream.append(console.ControlKey.CTRL_A)
- exp_console_out += OutputStream.MoveCursorLeft(self.console.line_limit)
-
- # Press CTRL+E to jump to the end of the line.
- input_stream.extend(Keys.END)
- exp_console_out += OutputStream.MoveCursorRight(self.console.line_limit)
-
- # Move left one column so we can delete a character.
- input_stream.extend(Keys.LEFT_ARROW)
- exp_console_out += OutputStream.MoveCursorLeft(1)
-
- # Press the delete key.
- input_stream.extend(Keys.DEL)
- # This should look like a space, and then move cursor left 1 column since
- # we're at the end of line.
- exp_console_out += b' ' + OutputStream.MoveCursorLeft(1)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify everything happened correctly.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_BackspaceOnEmptyLine(self):
- """Verify that we can backspace on an empty line with no bad effects."""
- # Send a single backspace.
- test_str = [console.ControlKey.BACKSPACE]
-
- # Send the characters in.
- for byte in test_str:
- self.console.HandleChar(byte)
-
- # Check the input position.
- exp_pos = 0
- CheckInputBufferPosition(self, exp_pos)
-
- # Check that buffer is empty.
- exp_input_buffer = b''
- CheckInputBuffer(self, exp_input_buffer)
-
- # Check that the console output is empty.
- exp_console_out = b''
- CheckConsoleOutput(self, exp_console_out)
-
- def test_BackspaceWithinLine(self):
- """Verify that we shift the chars over when backspacing within a line."""
- # Misspell 'help'
- test_str = b'heelp'
- input_stream = BytesToByteList(test_str)
- # Use the arrow key to go back to fix it.
- # Move cursor left 1 column.
- input_stream.extend(2*Keys.LEFT_ARROW)
- # Backspace once to remove the extra 'e'.
- input_stream.append(console.ControlKey.BACKSPACE)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify the input buffer
- exp_input_buffer = b'help'
- CheckInputBuffer(self, exp_input_buffer)
-
- # Verify the input buffer position. It should be at 2 (cursor over the 'l')
- CheckInputBufferPosition(self, 2)
-
- # We expect the console output to be the test string, with two moves to the
- # left, another move left, and then the rest of the line followed by a
- # space.
- exp_console_out = test_str
- exp_console_out += 2 * OutputStream.MoveCursorLeft(1)
-
- # Move cursor left 1 column.
- exp_console_out += OutputStream.MoveCursorLeft(1)
- # Rest of the line and a space. (test_str in this case)
- exp_console_out += b'lp '
- # Reset the cursor 2 + 1 to the left.
- exp_console_out += OutputStream.MoveCursorLeft(3)
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_JumpToBeginningOfLineViaCtrlA(self):
- """Verify that we can jump to the beginning of a line with Ctrl+A."""
- # Enter some chars and press CTRL+A
- test_str = b'abc'
- input_stream = BytesToByteList(test_str) + [console.ControlKey.CTRL_A]
-
- # Send the characters in.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # We expect to see our test string followed by a move cursor left.
- exp_console_out = test_str
- exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
-
- # Check to see what whas printed on the console.
- CheckConsoleOutput(self, exp_console_out)
-
- # Check that the input buffer position is now 0.
- CheckInputBufferPosition(self, 0)
-
- # Check input buffer still contains our test string.
- CheckInputBuffer(self, test_str)
-
- def test_JumpToBeginningOfLineViaHomeKey(self):
- """Jump to beginning of line via HOME key."""
- test_str = b'version'
- input_stream = BytesToByteList(test_str)
- input_stream.extend(Keys.HOME)
-
- # Send out the stream.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, verify that input buffer position is now 0.
- CheckInputBufferPosition(self, 0)
-
- # Next, verify that the input buffer did not change.
- CheckInputBuffer(self, test_str)
-
- # Lastly, check that the cursor moved correctly.
- exp_console_out = test_str
- exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
- CheckConsoleOutput(self, exp_console_out)
-
- def test_JumpToEndOfLineViaEndKey(self):
- """Jump to the end of the line using the END key."""
- test_str = b'version'
- input_stream = BytesToByteList(test_str)
- input_stream += [console.ControlKey.CTRL_A]
- # Now, jump to the end of the line.
- input_stream.extend(Keys.END)
-
- # Send out the stream.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the input buffer position is correct. This should be at the
- # end of the test string.
- CheckInputBufferPosition(self, len(test_str))
-
- # The expected output should be the test string, followed by a jump to the
- # beginning of the line, and lastly a jump to the end of the line.
- exp_console_out = test_str
- exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
- # Now the jump back to the end of the line.
- exp_console_out += OutputStream.MoveCursorRight(len(test_str))
-
- # Verify console output stream.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_JumpToEndOfLineViaCtrlE(self):
- """Enter some chars and then try to jump to the end. (Should be a no-op)"""
- test_str = b'sysinfo'
- input_stream = BytesToByteList(test_str)
- input_stream.append(console.ControlKey.CTRL_E)
-
- # Send out the stream
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the input buffer position isn't any further than we expect.
- # At this point, the position should be at the end of the test string.
- CheckInputBufferPosition(self, len(test_str))
-
- # Now, let's try to jump to the beginning and then jump back to the end.
- input_stream = [console.ControlKey.CTRL_A, console.ControlKey.CTRL_E]
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Perform the same verification.
- CheckInputBufferPosition(self, len(test_str))
-
- # Lastly try to jump again, beyond the end.
- input_stream = [console.ControlKey.CTRL_E]
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Perform the same verification.
- CheckInputBufferPosition(self, len(test_str))
-
- # We expect to see the test string, a jump to the beginning of the line, and
- # one jump to the end of the line.
- exp_console_out = test_str
- # Jump to beginning.
- exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
- # Jump back to end.
- exp_console_out += OutputStream.MoveCursorRight(len(test_str))
-
- # Verify the console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_MoveLeftWithArrowKey(self):
- """Move cursor left one column with arrow key."""
- test_str = b'tastyspam'
- input_stream = BytesToByteList(test_str)
- input_stream.extend(Keys.LEFT_ARROW)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the input buffer position is 1 less than the length.
- CheckInputBufferPosition(self, len(test_str) - 1)
-
- # Also, verify that the input buffer is not modified.
- CheckInputBuffer(self, test_str)
-
- # We expect the test string, followed by a one column move left.
- exp_console_out = test_str + OutputStream.MoveCursorLeft(1)
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_MoveLeftWithCtrlB(self):
- """Move cursor back one column with Ctrl+B."""
- test_str = b'tastyspam'
- input_stream = BytesToByteList(test_str)
- input_stream.append(console.ControlKey.CTRL_B)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the input buffer position is 1 less than the length.
- CheckInputBufferPosition(self, len(test_str) - 1)
+ Args:
+ test_case: A unittest.TestCase object representing the current unit test.
+ exp_pos: An integer representing the expected input buffer position.
+ """
+ test_case.assertEqual(
+ exp_pos,
+ test_case.console.input_buffer_pos,
+ "input buffer position is incorrect.\ngot: "
+ + str(test_case.console.input_buffer_pos)
+ + "\nexp: "
+ + str(exp_pos)
+ + "\n"
+ + str(test_case.console),
+ )
- # Also, verify that the input buffer is not modified.
- CheckInputBuffer(self, test_str)
- # We expect the test string, followed by a one column move left.
- exp_console_out = test_str + OutputStream.MoveCursorLeft(1)
+def CheckHistoryBuffer(test_case, exp_history):
+ """Verify that the items in the history buffer are what we expect.
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
+ Args:
+ test_case: A unittest.TestCase object representing the current unit test.
+ exp_history: A list of strings representing the expected contents of the
+ history buffer.
+ """
+ # First, check to see if the length is what we expect.
+ test_case.assertEqual(
+ len(exp_history),
+ len(test_case.console.history),
+ (
+ "The number of items in the history is unexpected.\n"
+ "exp: " + str(len(exp_history)) + "\n"
+ "got: " + str(len(test_case.console.history)) + "\n"
+ "internal state:\n" + str(test_case.console)
+ ),
+ )
+
+ # Next, check the actual contents of the history buffer.
+ for i in range(len(exp_history)):
+ test_case.assertEqual(
+ exp_history[i],
+ test_case.console.history[i],
+ (
+ b"history buffer contents are incorrect.\n"
+ b"exp: " + exp_history[i] + b"\n"
+ b"got: " + test_case.console.history[i] + b"\n"
+ b"internal state:\n" + str(test_case.console).encode("ascii")
+ ),
+ )
- def test_MoveRightWithArrowKey(self):
- """Move cursor one column to the right with the arrow key."""
- test_str = b'version'
- input_stream = BytesToByteList(test_str)
- # Jump to beginning of line.
- input_stream.append(console.ControlKey.CTRL_A)
- # Press right arrow key.
- input_stream.extend(Keys.RIGHT_ARROW)
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
+class TestConsoleEditingMethods(unittest.TestCase):
+ """Test case to verify all console editing methods."""
+
+ def setUp(self):
+ """Setup the test harness."""
+ # Setup logging with a timestamp, the module, and the log level.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+
+ # Create a temp file and set both the controller and peripheral PTYs to the
+ # file to create a loopback.
+ self.tempfile = tempfile.TemporaryFile()
+
+ # Create some mock pipes. These won't be used since we'll mock out sends
+ # to the interpreter.
+ mock_pipe_end_0, mock_pipe_end_1 = threadproc_shim.Pipe()
+ self.console = console.Console(
+ self.tempfile.fileno(),
+ self.tempfile,
+ tempfile.TemporaryFile(),
+ mock_pipe_end_0,
+ mock_pipe_end_1,
+ "EC",
+ )
+
+ # Console editing methods are only valid for enhanced EC images, therefore
+ # we have to assume that the "EC" we're talking to is enhanced. By default,
+ # the console believes that the EC it's communicating with is NOT enhanced
+ # which is why we have to override it here.
+ self.console.enhanced_ec = True
+ self.console.CheckForEnhancedECImage = mock.MagicMock(return_value=True)
+
+ def test_EnteringChars(self):
+ """Verify that characters are echoed onto the console."""
+ test_str = b"abc"
+ input_stream = BytesToByteList(test_str)
+
+ # Send the characters in.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Check the input position.
+ exp_pos = len(test_str)
+ CheckInputBufferPosition(self, exp_pos)
+
+ # Verify that the input buffer is correct.
+ expected_buffer = test_str
+ CheckInputBuffer(self, expected_buffer)
+
+ # Check console output
+ exp_console_out = test_str
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_EnteringDeletingMoreCharsThanEntered(self):
+ """Verify that we can press backspace more than we have entered chars."""
+ test_str = b"spamspam"
+ input_stream = BytesToByteList(test_str)
+
+ # Send the characters in.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Now backspace 1 more than what we sent.
+ input_stream = []
+ for _ in range(len(test_str) + 1):
+ input_stream.append(console.ControlKey.BACKSPACE)
+
+ # Send that sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, verify that input buffer position is 0.
+ CheckInputBufferPosition(self, 0)
+
+ # Next, examine the output stream for the correct sequence.
+ exp_console_out = test_str
+ for _ in range(len(test_str)):
+ exp_console_out += BACKSPACE_STRING
+
+ # Now, verify that we got what we expected.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_EnteringMoreThanCharLimit(self):
+ """Verify that we drop characters when the line is too long."""
+ test_str = self.console.line_limit * b"o" # All allowed.
+ test_str += 5 * b"x" # All should be dropped.
+ input_stream = BytesToByteList(test_str)
+
+ # Send the characters in.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, we expect that input buffer position should be equal to the line
+ # limit.
+ exp_pos = self.console.line_limit
+ CheckInputBufferPosition(self, exp_pos)
+
+ # The input buffer should only hold until the line limit.
+ exp_buffer = test_str[0 : self.console.line_limit]
+ CheckInputBuffer(self, exp_buffer)
+
+ # Lastly, check that the extra characters are not printed.
+ exp_console_out = exp_buffer
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_ValidKeysOnLongLine(self):
+ """Verify that we can still press valid keys if the line is too long."""
+ # Fill the line.
+ test_str = self.console.line_limit * b"o"
+ exp_console_out = test_str
+ # Try to fill it even more; these should all be dropped.
+ test_str += 5 * b"x"
+ input_stream = BytesToByteList(test_str)
+
+ # We should be able to press the following keys:
+ # - Backspace
+ # - Arrow Keys/CTRL+B/CTRL+F/CTRL+P/CTRL+N
+ # - Delete
+ # - Home/CTRL+A
+ # - End/CTRL+E
+ # - Carriage Return
+
+ # Backspace 1 character
+ input_stream.append(console.ControlKey.BACKSPACE)
+ exp_console_out += BACKSPACE_STRING
+ # Refill the line.
+ input_stream.extend(BytesToByteList(b"o"))
+ exp_console_out += b"o"
+
+ # Left arrow key.
+ input_stream.extend(Keys.LEFT_ARROW)
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+
+ # Right arrow key.
+ input_stream.extend(Keys.RIGHT_ARROW)
+ exp_console_out += OutputStream.MoveCursorRight(1)
+
+ # CTRL+B
+ input_stream.append(console.ControlKey.CTRL_B)
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+
+ # CTRL+F
+ input_stream.append(console.ControlKey.CTRL_F)
+ exp_console_out += OutputStream.MoveCursorRight(1)
+
+ # Let's press enter now so we can test up and down.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ exp_console_out += b"\r\n" + self.console.prompt
+
+ # Up arrow key.
+ input_stream.extend(Keys.UP_ARROW)
+ exp_console_out += test_str[: self.console.line_limit]
+
+ # Down arrow key.
+ input_stream.extend(Keys.DOWN_ARROW)
+ # Since the line was blank, we have to backspace the entire line.
+ exp_console_out += self.console.line_limit * BACKSPACE_STRING
+
+ # CTRL+P
+ input_stream.append(console.ControlKey.CTRL_P)
+ exp_console_out += test_str[: self.console.line_limit]
+
+ # CTRL+N
+ input_stream.append(console.ControlKey.CTRL_N)
+ # Since the line was blank, we have to backspace the entire line.
+ exp_console_out += self.console.line_limit * BACKSPACE_STRING
+
+ # Press the Up arrow key to reprint the long line.
+ input_stream.extend(Keys.UP_ARROW)
+ exp_console_out += test_str[: self.console.line_limit]
+
+ # Press the Home key to jump to the beginning of the line.
+ input_stream.extend(Keys.HOME)
+ exp_console_out += OutputStream.MoveCursorLeft(self.console.line_limit)
+
+ # Press the End key to jump to the end of the line.
+ input_stream.extend(Keys.END)
+ exp_console_out += OutputStream.MoveCursorRight(self.console.line_limit)
+
+ # Press CTRL+A to jump to the beginning of the line.
+ input_stream.append(console.ControlKey.CTRL_A)
+ exp_console_out += OutputStream.MoveCursorLeft(self.console.line_limit)
+
+ # Press CTRL+E to jump to the end of the line.
+ input_stream.extend(Keys.END)
+ exp_console_out += OutputStream.MoveCursorRight(self.console.line_limit)
+
+ # Move left one column so we can delete a character.
+ input_stream.extend(Keys.LEFT_ARROW)
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+
+ # Press the delete key.
+ input_stream.extend(Keys.DEL)
+ # This should look like a space, and then move cursor left 1 column since
+ # we're at the end of line.
+ exp_console_out += b" " + OutputStream.MoveCursorLeft(1)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify everything happened correctly.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_BackspaceOnEmptyLine(self):
+ """Verify that we can backspace on an empty line with no bad effects."""
+ # Send a single backspace.
+ test_str = [console.ControlKey.BACKSPACE]
+
+ # Send the characters in.
+ for byte in test_str:
+ self.console.HandleChar(byte)
+
+ # Check the input position.
+ exp_pos = 0
+ CheckInputBufferPosition(self, exp_pos)
+
+ # Check that buffer is empty.
+ exp_input_buffer = b""
+ CheckInputBuffer(self, exp_input_buffer)
+
+ # Check that the console output is empty.
+ exp_console_out = b""
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_BackspaceWithinLine(self):
+ """Verify that we shift the chars over when backspacing within a line."""
+ # Misspell 'help'
+ test_str = b"heelp"
+ input_stream = BytesToByteList(test_str)
+ # Use the arrow key to go back to fix it.
+ # Move cursor left 1 column.
+ input_stream.extend(2 * Keys.LEFT_ARROW)
+ # Backspace once to remove the extra 'e'.
+ input_stream.append(console.ControlKey.BACKSPACE)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify the input buffer
+ exp_input_buffer = b"help"
+ CheckInputBuffer(self, exp_input_buffer)
+
+ # Verify the input buffer position. It should be at 2 (cursor over the 'l')
+ CheckInputBufferPosition(self, 2)
+
+ # We expect the console output to be the test string, with two moves to the
+ # left, another move left, and then the rest of the line followed by a
+ # space.
+ exp_console_out = test_str
+ exp_console_out += 2 * OutputStream.MoveCursorLeft(1)
+
+ # Move cursor left 1 column.
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+ # Rest of the line and a space. (test_str in this case)
+ exp_console_out += b"lp "
+ # Reset the cursor 2 + 1 to the left.
+ exp_console_out += OutputStream.MoveCursorLeft(3)
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_JumpToBeginningOfLineViaCtrlA(self):
+ """Verify that we can jump to the beginning of a line with Ctrl+A."""
+ # Enter some chars and press CTRL+A
+ test_str = b"abc"
+ input_stream = BytesToByteList(test_str) + [console.ControlKey.CTRL_A]
+
+ # Send the characters in.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # We expect to see our test string followed by a move cursor left.
+ exp_console_out = test_str
+ exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
+
+ # Check to see what whas printed on the console.
+ CheckConsoleOutput(self, exp_console_out)
+
+ # Check that the input buffer position is now 0.
+ CheckInputBufferPosition(self, 0)
+
+ # Check input buffer still contains our test string.
+ CheckInputBuffer(self, test_str)
+
+ def test_JumpToBeginningOfLineViaHomeKey(self):
+ """Jump to beginning of line via HOME key."""
+ test_str = b"version"
+ input_stream = BytesToByteList(test_str)
+ input_stream.extend(Keys.HOME)
+
+ # Send out the stream.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, verify that input buffer position is now 0.
+ CheckInputBufferPosition(self, 0)
+
+ # Next, verify that the input buffer did not change.
+ CheckInputBuffer(self, test_str)
+
+ # Lastly, check that the cursor moved correctly.
+ exp_console_out = test_str
+ exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_JumpToEndOfLineViaEndKey(self):
+ """Jump to the end of the line using the END key."""
+ test_str = b"version"
+ input_stream = BytesToByteList(test_str)
+ input_stream += [console.ControlKey.CTRL_A]
+ # Now, jump to the end of the line.
+ input_stream.extend(Keys.END)
+
+ # Send out the stream.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the input buffer position is correct. This should be at the
+ # end of the test string.
+ CheckInputBufferPosition(self, len(test_str))
+
+ # The expected output should be the test string, followed by a jump to the
+ # beginning of the line, and lastly a jump to the end of the line.
+ exp_console_out = test_str
+ exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
+ # Now the jump back to the end of the line.
+ exp_console_out += OutputStream.MoveCursorRight(len(test_str))
+
+ # Verify console output stream.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_JumpToEndOfLineViaCtrlE(self):
+ """Enter some chars and then try to jump to the end. (Should be a no-op)"""
+ test_str = b"sysinfo"
+ input_stream = BytesToByteList(test_str)
+ input_stream.append(console.ControlKey.CTRL_E)
+
+ # Send out the stream
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the input buffer position isn't any further than we expect.
+ # At this point, the position should be at the end of the test string.
+ CheckInputBufferPosition(self, len(test_str))
+
+ # Now, let's try to jump to the beginning and then jump back to the end.
+ input_stream = [console.ControlKey.CTRL_A, console.ControlKey.CTRL_E]
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Perform the same verification.
+ CheckInputBufferPosition(self, len(test_str))
+
+ # Lastly try to jump again, beyond the end.
+ input_stream = [console.ControlKey.CTRL_E]
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Perform the same verification.
+ CheckInputBufferPosition(self, len(test_str))
+
+ # We expect to see the test string, a jump to the beginning of the line, and
+ # one jump to the end of the line.
+ exp_console_out = test_str
+ # Jump to beginning.
+ exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
+ # Jump back to end.
+ exp_console_out += OutputStream.MoveCursorRight(len(test_str))
+
+ # Verify the console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_MoveLeftWithArrowKey(self):
+ """Move cursor left one column with arrow key."""
+ test_str = b"tastyspam"
+ input_stream = BytesToByteList(test_str)
+ input_stream.extend(Keys.LEFT_ARROW)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the input buffer position is 1 less than the length.
+ CheckInputBufferPosition(self, len(test_str) - 1)
+
+ # Also, verify that the input buffer is not modified.
+ CheckInputBuffer(self, test_str)
+
+ # We expect the test string, followed by a one column move left.
+ exp_console_out = test_str + OutputStream.MoveCursorLeft(1)
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_MoveLeftWithCtrlB(self):
+ """Move cursor back one column with Ctrl+B."""
+ test_str = b"tastyspam"
+ input_stream = BytesToByteList(test_str)
+ input_stream.append(console.ControlKey.CTRL_B)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the input buffer position is 1 less than the length.
+ CheckInputBufferPosition(self, len(test_str) - 1)
- # Verify that the input buffer position is 1.
- CheckInputBufferPosition(self, 1)
+ # Also, verify that the input buffer is not modified.
+ CheckInputBuffer(self, test_str)
- # Also, verify that the input buffer is not modified.
- CheckInputBuffer(self, test_str)
+ # We expect the test string, followed by a one column move left.
+ exp_console_out = test_str + OutputStream.MoveCursorLeft(1)
- # We expect the test string, followed by a jump to the beginning of the
- # line, and finally a move right 1.
- exp_console_out = test_str + OutputStream.MoveCursorLeft(len((test_str)))
-
- # A move right 1 column.
- exp_console_out += OutputStream.MoveCursorRight(1)
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_MoveRightWithCtrlF(self):
- """Move cursor forward one column with Ctrl+F."""
- test_str = b'panicinfo'
- input_stream = BytesToByteList(test_str)
- input_stream.append(console.ControlKey.CTRL_A)
- # Now, move right one column.
- input_stream.append(console.ControlKey.CTRL_F)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the input buffer position is 1.
- CheckInputBufferPosition(self, 1)
-
- # Also, verify that the input buffer is not modified.
- CheckInputBuffer(self, test_str)
-
- # We expect the test string, followed by a jump to the beginning of the
- # line, and finally a move right 1.
- exp_console_out = test_str + OutputStream.MoveCursorLeft(len((test_str)))
-
- # A move right 1 column.
- exp_console_out += OutputStream.MoveCursorRight(1)
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_ImpossibleMoveLeftWithArrowKey(self):
- """Verify that we can't move left at the beginning of the line."""
- # We shouldn't be able to move left if we're at the beginning of the line.
- input_stream = Keys.LEFT_ARROW
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Nothing should have been output.
- exp_console_output = b''
- CheckConsoleOutput(self, exp_console_output)
-
- # The input buffer position should still be 0.
- CheckInputBufferPosition(self, 0)
-
- # The input buffer itself should be empty.
- CheckInputBuffer(self, b'')
-
- def test_ImpossibleMoveRightWithArrowKey(self):
- """Verify that we can't move right at the end of the line."""
- # We shouldn't be able to move right if we're at the end of the line.
- input_stream = Keys.RIGHT_ARROW
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Nothing should have been output.
- exp_console_output = b''
- CheckConsoleOutput(self, exp_console_output)
-
- # The input buffer position should still be 0.
- CheckInputBufferPosition(self, 0)
-
- # The input buffer itself should be empty.
- CheckInputBuffer(self, b'')
-
- def test_KillEntireLine(self):
- """Verify that we can kill an entire line with Ctrl+K."""
- test_str = b'accelinfo on'
- input_stream = BytesToByteList(test_str)
- # Jump to beginning of line and then kill it with Ctrl+K.
- input_stream.extend([console.ControlKey.CTRL_A, console.ControlKey.CTRL_K])
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, we expect that the input buffer is empty.
- CheckInputBuffer(self, b'')
-
- # The buffer position should be 0.
- CheckInputBufferPosition(self, 0)
-
- # What we expect to see on the console stream should be the following. The
- # test string, a jump to the beginning of the line, then jump back to the
- # end of the line and replace the line with spaces.
- exp_console_out = test_str
- # Jump to beginning of line.
- exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
- # Jump to end of line.
- exp_console_out += OutputStream.MoveCursorRight(len(test_str))
- # Replace line with spaces, which looks like backspaces.
- for _ in range(len(test_str)):
- exp_console_out += BACKSPACE_STRING
-
- # Verify the console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_KillPartialLine(self):
- """Verify that we can kill a portion of a line."""
- test_str = b'accelread 0 1'
- input_stream = BytesToByteList(test_str)
- len_to_kill = 5
- for _ in range(len_to_kill):
- # Move cursor left
- input_stream.extend(Keys.LEFT_ARROW)
- # Now kill
- input_stream.append(console.ControlKey.CTRL_K)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, check that the input buffer was truncated.
- exp_input_buffer = test_str[:-len_to_kill]
- CheckInputBuffer(self, exp_input_buffer)
-
- # Verify the input buffer position.
- CheckInputBufferPosition(self, len(test_str) - len_to_kill)
-
- # The console output stream that we expect is the test string followed by a
- # move left of len_to_kill, then a jump to the end of the line and backspace
- # of len_to_kill.
- exp_console_out = test_str
- for _ in range(len_to_kill):
- # Move left 1 column.
- exp_console_out += OutputStream.MoveCursorLeft(1)
- # Then jump to the end of the line
- exp_console_out += OutputStream.MoveCursorRight(len_to_kill)
- # Backspace of len_to_kill
- for _ in range(len_to_kill):
- exp_console_out += BACKSPACE_STRING
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_InsertingCharacters(self):
- """Verify that we can insert characters within the line."""
- test_str = b'accel 0 1' # Here we forgot the 'read' part in 'accelread'
- input_stream = BytesToByteList(test_str)
- # We need to move over to the 'l' and add read.
- insertion_point = test_str.find(b'l') + 1
- for i in range(len(test_str) - insertion_point):
- # Move cursor left.
- input_stream.extend(Keys.LEFT_ARROW)
- # Now, add in 'read'
- added_str = b'read'
- input_stream.extend(BytesToByteList(added_str))
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # First, verify that the input buffer is correct.
- exp_input_buffer = test_str[:insertion_point] + added_str
- exp_input_buffer += test_str[insertion_point:]
- CheckInputBuffer(self, exp_input_buffer)
-
- # Verify that the input buffer position is correct.
- exp_input_buffer_pos = insertion_point + len(added_str)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
-
- # The console output stream that we expect is the test string, followed by
- # move cursor left until the 'l' was found, the added test string while
- # shifting characters around.
- exp_console_out = test_str
- for i in range(len(test_str) - insertion_point):
- # Move cursor left.
- exp_console_out += OutputStream.MoveCursorLeft(1)
-
- # Now for each character, write the rest of the line will be shifted to the
- # right one column.
- for i in range(len(added_str)):
- # Printed character.
- exp_console_out += added_str[i:i+1]
- # The rest of the line
- exp_console_out += test_str[insertion_point:]
- # Reset the cursor back left
- reset_dist = len(test_str[insertion_point:])
- exp_console_out += OutputStream.MoveCursorLeft(reset_dist)
-
- # Verify the console output.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_StoreCommandHistory(self):
- """Verify that entered commands are stored in the history."""
- test_commands = []
- test_commands.append(b'help')
- test_commands.append(b'version')
- test_commands.append(b'accelread 0 1')
- input_stream = []
- for c in test_commands:
- input_stream.extend(BytesToByteList(c))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # We expect to have the test commands in the history buffer.
- exp_history_buf = test_commands
- CheckHistoryBuffer(self, exp_history_buf)
-
- def test_CycleUpThruCommandHistory(self):
- """Verify that the UP arrow key will print itmes in the history buffer."""
- # Enter some commands.
- test_commands = [b'version', b'accelrange 0', b'battery', b'gettime']
- input_stream = []
- for command in test_commands:
- input_stream.extend(BytesToByteList(command))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Now, hit the UP arrow key to print the previous entries.
- for i in range(len(test_commands)):
- input_stream.extend(Keys.UP_ARROW)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The expected output should be test commands with prompts printed in
- # between, followed by line kills with the previous test commands printed.
- exp_console_out = b''
- for i in range(len(test_commands)):
- exp_console_out += test_commands[i] + b'\r\n' + self.console.prompt
-
- # When we press up, the line should be cleared and print the previous buffer
- # entry.
- for i in range(len(test_commands)-1, 0, -1):
- exp_console_out += test_commands[i]
- # Backspace to the beginning.
- for i in range(len(test_commands[i])):
- exp_console_out += BACKSPACE_STRING
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
- # The last command should just be printed out with no backspacing.
- exp_console_out += test_commands[0]
-
- # Now, verify.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_UpArrowOnEmptyHistory(self):
- """Ensure nothing happens if the history is empty."""
- # Press the up arrow key twice.
- input_stream = 2 * Keys.UP_ARROW
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # We expect nothing to have happened.
- exp_console_out = b''
- exp_input_buffer = b''
- exp_input_buffer_pos = 0
- exp_history_buf = []
-
- # Verify.
- CheckConsoleOutput(self, exp_console_out)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
- CheckInputBuffer(self, exp_input_buffer)
- CheckHistoryBuffer(self, exp_history_buf)
-
- def test_UpArrowDoesNotGoOutOfBounds(self):
- """Verify that pressing the up arrow many times won't go out of bounds."""
- # Enter one command.
- test_str = b'help version'
- input_stream = BytesToByteList(test_str)
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- # Then press the up arrow key twice.
- input_stream.extend(2 * Keys.UP_ARROW)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the history buffer is correct.
- exp_history_buf = [test_str]
- CheckHistoryBuffer(self, exp_history_buf)
-
- # We expect that the console output should only contain our entered command,
- # a new prompt, and then our command aggain.
- exp_console_out = test_str + b'\r\n' + self.console.prompt
- # Pressing up should reprint the command we entered.
- exp_console_out += test_str
-
- # Verify.
- CheckConsoleOutput(self, exp_console_out)
-
- def test_CycleDownThruCommandHistory(self):
- """Verify that we can select entries by hitting the down arrow."""
- # Enter at least 4 commands.
- test_commands = [b'version', b'accelrange 0', b'battery', b'gettime']
- input_stream = []
- for command in test_commands:
- input_stream.extend(BytesToByteList(command))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Now, hit the UP arrow key twice to print the previous two entries.
- for i in range(2):
- input_stream.extend(Keys.UP_ARROW)
-
- # Now, hit the DOWN arrow key twice to print the newer entries.
- input_stream.extend(2*Keys.DOWN_ARROW)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The expected output should be commands that we entered, followed by
- # prompts, then followed by our last two commands in reverse. Then, we
- # should see the last entry in the list, followed by the saved partial cmd
- # of a blank line.
- exp_console_out = b''
- for i in range(len(test_commands)):
- exp_console_out += test_commands[i] + b'\r\n' + self.console.prompt
-
- # When we press up, the line should be cleared and print the previous buffer
- # entry.
- for i in range(len(test_commands)-1, 1, -1):
- exp_console_out += test_commands[i]
- # Backspace to the beginning.
- for i in range(len(test_commands[i])):
- exp_console_out += BACKSPACE_STRING
+ def test_MoveRightWithArrowKey(self):
+ """Move cursor one column to the right with the arrow key."""
+ test_str = b"version"
+ input_stream = BytesToByteList(test_str)
+ # Jump to beginning of line.
+ input_stream.append(console.ControlKey.CTRL_A)
+ # Press right arrow key.
+ input_stream.extend(Keys.RIGHT_ARROW)
- # When we press down, it should have cleared the last command (which we
- # covered with the previous for loop), and then prints the next command.
- exp_console_out += test_commands[3]
- for i in range(len(test_commands[3])):
- exp_console_out += BACKSPACE_STRING
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- # Verify input buffer.
- exp_input_buffer = b'' # Empty because our partial command was empty.
- exp_input_buffer_pos = len(exp_input_buffer)
- CheckInputBuffer(self, exp_input_buffer)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
-
- def test_SavingPartialCommandWhenNavigatingHistory(self):
- """Verify that partial commands are saved when navigating history."""
- # Enter a command.
- test_str = b'accelinfo'
- input_stream = BytesToByteList(test_str)
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Enter a partial command.
- partial_cmd = b'ver'
- input_stream.extend(BytesToByteList(partial_cmd))
-
- # Hit the UP arrow key.
- input_stream.extend(Keys.UP_ARROW)
- # Then, the DOWN arrow key.
- input_stream.extend(Keys.DOWN_ARROW)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The expected output should be the command we entered, a prompt, the
- # partial command, clearing of the partial command, the command entered,
- # clearing of the command entered, and then the partial command.
- exp_console_out = test_str + b'\r\n' + self.console.prompt
- exp_console_out += partial_cmd
- for _ in range(len(partial_cmd)):
- exp_console_out += BACKSPACE_STRING
- exp_console_out += test_str
- for _ in range(len(test_str)):
- exp_console_out += BACKSPACE_STRING
- exp_console_out += partial_cmd
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- # Verify input buffer.
- exp_input_buffer = partial_cmd
- exp_input_buffer_pos = len(exp_input_buffer)
- CheckInputBuffer(self, exp_input_buffer)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
-
- def test_DownArrowOnEmptyHistory(self):
- """Ensure nothing happens if the history is empty."""
- # Then press the up down arrow twice.
- input_stream = 2 * Keys.DOWN_ARROW
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # We expect nothing to have happened.
- exp_console_out = b''
- exp_input_buffer = b''
- exp_input_buffer_pos = 0
- exp_history_buf = []
-
- # Verify.
- CheckConsoleOutput(self, exp_console_out)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
- CheckInputBuffer(self, exp_input_buffer)
- CheckHistoryBuffer(self, exp_history_buf)
-
- def test_DeleteCharsUsingDELKey(self):
- """Verify that we can delete characters using the DEL key."""
- test_str = b'version'
- input_stream = BytesToByteList(test_str)
-
- # Hit the left arrow key 2 times.
- input_stream.extend(2 * Keys.LEFT_ARROW)
-
- # Press the DEL key.
- input_stream.extend(Keys.DEL)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The expected output should be the command we entered, 2 individual cursor
- # moves to the left, and then removing a char and shifting everything to the
- # left one column.
- exp_console_out = test_str
- exp_console_out += 2 * OutputStream.MoveCursorLeft(1)
-
- # Remove the char by shifting everything to the left one, slicing out the
- # remove char.
- exp_console_out += test_str[-1:] + b' '
-
- # Reset the cursor by moving back 2 columns because of the 'n' and space.
- exp_console_out += OutputStream.MoveCursorLeft(2)
-
- # Verify console output.
- CheckConsoleOutput(self, exp_console_out)
-
- # Verify input buffer. The input buffer should have the char sliced out and
- # be positioned where the char was removed.
- exp_input_buffer = test_str[:-2] + test_str[-1:]
- exp_input_buffer_pos = len(exp_input_buffer) - 1
- CheckInputBuffer(self, exp_input_buffer)
- CheckInputBufferPosition(self, exp_input_buffer_pos)
-
- def test_RepeatedCommandInHistory(self):
- """Verify that we don't store 2 consecutive identical commands in history"""
- # Enter a few commands.
- test_commands = [b'version', b'accelrange 0', b'battery', b'gettime']
- # Repeat the last command.
- test_commands.append(test_commands[len(test_commands)-1])
-
- input_stream = []
- for command in test_commands:
- input_stream.extend(BytesToByteList(command))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Verify that the history buffer is correct. The last command, since
- # it was repeated, should not have been added to the history.
- exp_history_buf = test_commands[0:len(test_commands)-1]
- CheckHistoryBuffer(self, exp_history_buf)
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+ # Verify that the input buffer position is 1.
+ CheckInputBufferPosition(self, 1)
-class TestConsoleCompatibility(unittest.TestCase):
- """Verify that console can speak to enhanced and non-enhanced EC images."""
- def setUp(self):
- """Setup the test harness."""
- # Setup logging with a timestamp, the module, and the log level.
- logging.basicConfig(level=logging.DEBUG,
- format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
- # Create a temp file and set both the controller and peripheral PTYs to the
- # file to create a loopback.
- self.tempfile = tempfile.TemporaryFile()
-
- # Mock out the pipes.
- mock_pipe_end_0, mock_pipe_end_1 = mock.MagicMock(), mock.MagicMock()
- self.console = console.Console(self.tempfile.fileno(), self.tempfile,
- tempfile.TemporaryFile(),
- mock_pipe_end_0, mock_pipe_end_1, "EC")
-
- @mock.patch('ec3po.console.Console.CheckForEnhancedECImage')
- def test_ActAsPassThruInNonEnhancedMode(self, mock_check):
- """Verify we simply pass everything thru to non-enhanced ECs.
+ # Also, verify that the input buffer is not modified.
+ CheckInputBuffer(self, test_str)
- Args:
- mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
- method.
- """
- # Set the interrogation mode to always so that we actually interrogate.
- self.console.interrogation_mode = b'always'
-
- # Assume EC interrogations indicate that the image is non-enhanced.
- mock_check.return_value = False
-
- # Press enter, followed by the command, and another enter.
- input_stream = []
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- test_command = b'version'
- input_stream.extend(BytesToByteList(test_command))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Expected calls to send down the pipe would be each character of the test
- # command.
- expected_calls = []
- expected_calls.append(mock.call(
- six.int2byte(console.ControlKey.CARRIAGE_RETURN)))
- for char in test_command:
- if six.PY3:
- expected_calls.append(mock.call(bytes([char])))
- else:
- expected_calls.append(mock.call(char))
- expected_calls.append(mock.call(
- six.int2byte(console.ControlKey.CARRIAGE_RETURN)))
-
- # Verify that the calls happened.
- self.console.cmd_pipe.send.assert_has_calls(expected_calls)
-
- # Since we're acting as a pass-thru, the input buffer should be empty and
- # input_buffer_pos is 0.
- CheckInputBuffer(self, b'')
- CheckInputBufferPosition(self, 0)
-
- @mock.patch('ec3po.console.Console.CheckForEnhancedECImage')
- def test_TransitionFromNonEnhancedToEnhanced(self, mock_check):
- """Verify that we transition correctly to enhanced mode.
+ # We expect the test string, followed by a jump to the beginning of the
+ # line, and finally a move right 1.
+ exp_console_out = test_str + OutputStream.MoveCursorLeft(len((test_str)))
+
+ # A move right 1 column.
+ exp_console_out += OutputStream.MoveCursorRight(1)
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_MoveRightWithCtrlF(self):
+ """Move cursor forward one column with Ctrl+F."""
+ test_str = b"panicinfo"
+ input_stream = BytesToByteList(test_str)
+ input_stream.append(console.ControlKey.CTRL_A)
+ # Now, move right one column.
+ input_stream.append(console.ControlKey.CTRL_F)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the input buffer position is 1.
+ CheckInputBufferPosition(self, 1)
+
+ # Also, verify that the input buffer is not modified.
+ CheckInputBuffer(self, test_str)
+
+ # We expect the test string, followed by a jump to the beginning of the
+ # line, and finally a move right 1.
+ exp_console_out = test_str + OutputStream.MoveCursorLeft(len((test_str)))
+
+ # A move right 1 column.
+ exp_console_out += OutputStream.MoveCursorRight(1)
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_ImpossibleMoveLeftWithArrowKey(self):
+ """Verify that we can't move left at the beginning of the line."""
+ # We shouldn't be able to move left if we're at the beginning of the line.
+ input_stream = Keys.LEFT_ARROW
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Nothing should have been output.
+ exp_console_output = b""
+ CheckConsoleOutput(self, exp_console_output)
+
+ # The input buffer position should still be 0.
+ CheckInputBufferPosition(self, 0)
+
+ # The input buffer itself should be empty.
+ CheckInputBuffer(self, b"")
+
+ def test_ImpossibleMoveRightWithArrowKey(self):
+ """Verify that we can't move right at the end of the line."""
+ # We shouldn't be able to move right if we're at the end of the line.
+ input_stream = Keys.RIGHT_ARROW
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Nothing should have been output.
+ exp_console_output = b""
+ CheckConsoleOutput(self, exp_console_output)
+
+ # The input buffer position should still be 0.
+ CheckInputBufferPosition(self, 0)
+
+ # The input buffer itself should be empty.
+ CheckInputBuffer(self, b"")
+
+ def test_KillEntireLine(self):
+ """Verify that we can kill an entire line with Ctrl+K."""
+ test_str = b"accelinfo on"
+ input_stream = BytesToByteList(test_str)
+ # Jump to beginning of line and then kill it with Ctrl+K.
+ input_stream.extend([console.ControlKey.CTRL_A, console.ControlKey.CTRL_K])
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, we expect that the input buffer is empty.
+ CheckInputBuffer(self, b"")
+
+ # The buffer position should be 0.
+ CheckInputBufferPosition(self, 0)
+
+ # What we expect to see on the console stream should be the following. The
+ # test string, a jump to the beginning of the line, then jump back to the
+ # end of the line and replace the line with spaces.
+ exp_console_out = test_str
+ # Jump to beginning of line.
+ exp_console_out += OutputStream.MoveCursorLeft(len(test_str))
+ # Jump to end of line.
+ exp_console_out += OutputStream.MoveCursorRight(len(test_str))
+ # Replace line with spaces, which looks like backspaces.
+ for _ in range(len(test_str)):
+ exp_console_out += BACKSPACE_STRING
+
+ # Verify the console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_KillPartialLine(self):
+ """Verify that we can kill a portion of a line."""
+ test_str = b"accelread 0 1"
+ input_stream = BytesToByteList(test_str)
+ len_to_kill = 5
+ for _ in range(len_to_kill):
+ # Move cursor left
+ input_stream.extend(Keys.LEFT_ARROW)
+ # Now kill
+ input_stream.append(console.ControlKey.CTRL_K)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, check that the input buffer was truncated.
+ exp_input_buffer = test_str[:-len_to_kill]
+ CheckInputBuffer(self, exp_input_buffer)
+
+ # Verify the input buffer position.
+ CheckInputBufferPosition(self, len(test_str) - len_to_kill)
+
+ # The console output stream that we expect is the test string followed by a
+ # move left of len_to_kill, then a jump to the end of the line and backspace
+ # of len_to_kill.
+ exp_console_out = test_str
+ for _ in range(len_to_kill):
+ # Move left 1 column.
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+ # Then jump to the end of the line
+ exp_console_out += OutputStream.MoveCursorRight(len_to_kill)
+ # Backspace of len_to_kill
+ for _ in range(len_to_kill):
+ exp_console_out += BACKSPACE_STRING
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_InsertingCharacters(self):
+ """Verify that we can insert characters within the line."""
+ test_str = b"accel 0 1" # Here we forgot the 'read' part in 'accelread'
+ input_stream = BytesToByteList(test_str)
+ # We need to move over to the 'l' and add read.
+ insertion_point = test_str.find(b"l") + 1
+ for i in range(len(test_str) - insertion_point):
+ # Move cursor left.
+ input_stream.extend(Keys.LEFT_ARROW)
+ # Now, add in 'read'
+ added_str = b"read"
+ input_stream.extend(BytesToByteList(added_str))
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # First, verify that the input buffer is correct.
+ exp_input_buffer = test_str[:insertion_point] + added_str
+ exp_input_buffer += test_str[insertion_point:]
+ CheckInputBuffer(self, exp_input_buffer)
+
+ # Verify that the input buffer position is correct.
+ exp_input_buffer_pos = insertion_point + len(added_str)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+
+ # The console output stream that we expect is the test string, followed by
+ # move cursor left until the 'l' was found, the added test string while
+ # shifting characters around.
+ exp_console_out = test_str
+ for i in range(len(test_str) - insertion_point):
+ # Move cursor left.
+ exp_console_out += OutputStream.MoveCursorLeft(1)
+
+ # Now for each character, write the rest of the line will be shifted to the
+ # right one column.
+ for i in range(len(added_str)):
+ # Printed character.
+ exp_console_out += added_str[i : i + 1]
+ # The rest of the line
+ exp_console_out += test_str[insertion_point:]
+ # Reset the cursor back left
+ reset_dist = len(test_str[insertion_point:])
+ exp_console_out += OutputStream.MoveCursorLeft(reset_dist)
+
+ # Verify the console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_StoreCommandHistory(self):
+ """Verify that entered commands are stored in the history."""
+ test_commands = []
+ test_commands.append(b"help")
+ test_commands.append(b"version")
+ test_commands.append(b"accelread 0 1")
+ input_stream = []
+ for c in test_commands:
+ input_stream.extend(BytesToByteList(c))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # We expect to have the test commands in the history buffer.
+ exp_history_buf = test_commands
+ CheckHistoryBuffer(self, exp_history_buf)
+
+ def test_CycleUpThruCommandHistory(self):
+ """Verify that the UP arrow key will print itmes in the history buffer."""
+ # Enter some commands.
+ test_commands = [b"version", b"accelrange 0", b"battery", b"gettime"]
+ input_stream = []
+ for command in test_commands:
+ input_stream.extend(BytesToByteList(command))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Now, hit the UP arrow key to print the previous entries.
+ for i in range(len(test_commands)):
+ input_stream.extend(Keys.UP_ARROW)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The expected output should be test commands with prompts printed in
+ # between, followed by line kills with the previous test commands printed.
+ exp_console_out = b""
+ for i in range(len(test_commands)):
+ exp_console_out += test_commands[i] + b"\r\n" + self.console.prompt
+
+ # When we press up, the line should be cleared and print the previous buffer
+ # entry.
+ for i in range(len(test_commands) - 1, 0, -1):
+ exp_console_out += test_commands[i]
+ # Backspace to the beginning.
+ for i in range(len(test_commands[i])):
+ exp_console_out += BACKSPACE_STRING
+
+ # The last command should just be printed out with no backspacing.
+ exp_console_out += test_commands[0]
+
+ # Now, verify.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_UpArrowOnEmptyHistory(self):
+ """Ensure nothing happens if the history is empty."""
+ # Press the up arrow key twice.
+ input_stream = 2 * Keys.UP_ARROW
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # We expect nothing to have happened.
+ exp_console_out = b""
+ exp_input_buffer = b""
+ exp_input_buffer_pos = 0
+ exp_history_buf = []
+
+ # Verify.
+ CheckConsoleOutput(self, exp_console_out)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+ CheckInputBuffer(self, exp_input_buffer)
+ CheckHistoryBuffer(self, exp_history_buf)
+
+ def test_UpArrowDoesNotGoOutOfBounds(self):
+ """Verify that pressing the up arrow many times won't go out of bounds."""
+ # Enter one command.
+ test_str = b"help version"
+ input_stream = BytesToByteList(test_str)
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ # Then press the up arrow key twice.
+ input_stream.extend(2 * Keys.UP_ARROW)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the history buffer is correct.
+ exp_history_buf = [test_str]
+ CheckHistoryBuffer(self, exp_history_buf)
+
+ # We expect that the console output should only contain our entered command,
+ # a new prompt, and then our command aggain.
+ exp_console_out = test_str + b"\r\n" + self.console.prompt
+ # Pressing up should reprint the command we entered.
+ exp_console_out += test_str
+
+ # Verify.
+ CheckConsoleOutput(self, exp_console_out)
+
+ def test_CycleDownThruCommandHistory(self):
+ """Verify that we can select entries by hitting the down arrow."""
+ # Enter at least 4 commands.
+ test_commands = [b"version", b"accelrange 0", b"battery", b"gettime"]
+ input_stream = []
+ for command in test_commands:
+ input_stream.extend(BytesToByteList(command))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Now, hit the UP arrow key twice to print the previous two entries.
+ for i in range(2):
+ input_stream.extend(Keys.UP_ARROW)
+
+ # Now, hit the DOWN arrow key twice to print the newer entries.
+ input_stream.extend(2 * Keys.DOWN_ARROW)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The expected output should be commands that we entered, followed by
+ # prompts, then followed by our last two commands in reverse. Then, we
+ # should see the last entry in the list, followed by the saved partial cmd
+ # of a blank line.
+ exp_console_out = b""
+ for i in range(len(test_commands)):
+ exp_console_out += test_commands[i] + b"\r\n" + self.console.prompt
+
+ # When we press up, the line should be cleared and print the previous buffer
+ # entry.
+ for i in range(len(test_commands) - 1, 1, -1):
+ exp_console_out += test_commands[i]
+ # Backspace to the beginning.
+ for i in range(len(test_commands[i])):
+ exp_console_out += BACKSPACE_STRING
+
+ # When we press down, it should have cleared the last command (which we
+ # covered with the previous for loop), and then prints the next command.
+ exp_console_out += test_commands[3]
+ for i in range(len(test_commands[3])):
+ exp_console_out += BACKSPACE_STRING
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ # Verify input buffer.
+ exp_input_buffer = b"" # Empty because our partial command was empty.
+ exp_input_buffer_pos = len(exp_input_buffer)
+ CheckInputBuffer(self, exp_input_buffer)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+
+ def test_SavingPartialCommandWhenNavigatingHistory(self):
+ """Verify that partial commands are saved when navigating history."""
+ # Enter a command.
+ test_str = b"accelinfo"
+ input_stream = BytesToByteList(test_str)
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Enter a partial command.
+ partial_cmd = b"ver"
+ input_stream.extend(BytesToByteList(partial_cmd))
+
+ # Hit the UP arrow key.
+ input_stream.extend(Keys.UP_ARROW)
+ # Then, the DOWN arrow key.
+ input_stream.extend(Keys.DOWN_ARROW)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The expected output should be the command we entered, a prompt, the
+ # partial command, clearing of the partial command, the command entered,
+ # clearing of the command entered, and then the partial command.
+ exp_console_out = test_str + b"\r\n" + self.console.prompt
+ exp_console_out += partial_cmd
+ for _ in range(len(partial_cmd)):
+ exp_console_out += BACKSPACE_STRING
+ exp_console_out += test_str
+ for _ in range(len(test_str)):
+ exp_console_out += BACKSPACE_STRING
+ exp_console_out += partial_cmd
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ # Verify input buffer.
+ exp_input_buffer = partial_cmd
+ exp_input_buffer_pos = len(exp_input_buffer)
+ CheckInputBuffer(self, exp_input_buffer)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+
+ def test_DownArrowOnEmptyHistory(self):
+ """Ensure nothing happens if the history is empty."""
+ # Then press the up down arrow twice.
+ input_stream = 2 * Keys.DOWN_ARROW
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # We expect nothing to have happened.
+ exp_console_out = b""
+ exp_input_buffer = b""
+ exp_input_buffer_pos = 0
+ exp_history_buf = []
+
+ # Verify.
+ CheckConsoleOutput(self, exp_console_out)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+ CheckInputBuffer(self, exp_input_buffer)
+ CheckHistoryBuffer(self, exp_history_buf)
+
+ def test_DeleteCharsUsingDELKey(self):
+ """Verify that we can delete characters using the DEL key."""
+ test_str = b"version"
+ input_stream = BytesToByteList(test_str)
+
+ # Hit the left arrow key 2 times.
+ input_stream.extend(2 * Keys.LEFT_ARROW)
+
+ # Press the DEL key.
+ input_stream.extend(Keys.DEL)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The expected output should be the command we entered, 2 individual cursor
+ # moves to the left, and then removing a char and shifting everything to the
+ # left one column.
+ exp_console_out = test_str
+ exp_console_out += 2 * OutputStream.MoveCursorLeft(1)
+
+ # Remove the char by shifting everything to the left one, slicing out the
+ # remove char.
+ exp_console_out += test_str[-1:] + b" "
+
+ # Reset the cursor by moving back 2 columns because of the 'n' and space.
+ exp_console_out += OutputStream.MoveCursorLeft(2)
+
+ # Verify console output.
+ CheckConsoleOutput(self, exp_console_out)
+
+ # Verify input buffer. The input buffer should have the char sliced out and
+ # be positioned where the char was removed.
+ exp_input_buffer = test_str[:-2] + test_str[-1:]
+ exp_input_buffer_pos = len(exp_input_buffer) - 1
+ CheckInputBuffer(self, exp_input_buffer)
+ CheckInputBufferPosition(self, exp_input_buffer_pos)
+
+ def test_RepeatedCommandInHistory(self):
+ """Verify that we don't store 2 consecutive identical commands in history"""
+ # Enter a few commands.
+ test_commands = [b"version", b"accelrange 0", b"battery", b"gettime"]
+ # Repeat the last command.
+ test_commands.append(test_commands[len(test_commands) - 1])
+
+ input_stream = []
+ for command in test_commands:
+ input_stream.extend(BytesToByteList(command))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Verify that the history buffer is correct. The last command, since
+ # it was repeated, should not have been added to the history.
+ exp_history_buf = test_commands[0 : len(test_commands) - 1]
+ CheckHistoryBuffer(self, exp_history_buf)
- Args:
- mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
- method.
- """
- # Set the interrogation mode to always so that we actually interrogate.
- self.console.interrogation_mode = b'always'
-
- # First, assume that the EC interrogations indicate an enhanced EC image.
- mock_check.return_value = True
- # But our current knowledge of the EC image (which was actually the
- # 'previous' EC) was a non-enhanced image.
- self.console.enhanced_ec = False
-
- test_command = b'sysinfo'
- input_stream = []
- input_stream.extend(BytesToByteList(test_command))
-
- expected_calls = []
- # All keystrokes to the console should be directed straight through to the
- # EC until we press the enter key.
- for char in test_command:
- if six.PY3:
- expected_calls.append(mock.call(bytes([char])))
- else:
- expected_calls.append(mock.call(char))
-
- # Press the enter key.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- # The enter key should not be sent to the pipe since we should negotiate
- # to an enhanced EC image.
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # At this point, we should have negotiated to enhanced.
- self.assertTrue(self.console.enhanced_ec, msg=('Did not negotiate to '
- 'enhanced EC image.'))
-
- # The command would have been dropped however, so verify this...
- CheckInputBuffer(self, b'')
- CheckInputBufferPosition(self, 0)
- # ...and repeat the command.
- input_stream = BytesToByteList(test_command)
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Since we're enhanced now, we should have sent the entire command as one
- # string with no trailing carriage return
- expected_calls.append(mock.call(test_command))
-
- # Verify all of the calls.
- self.console.cmd_pipe.send.assert_has_calls(expected_calls)
-
- @mock.patch('ec3po.console.Console.CheckForEnhancedECImage')
- def test_TransitionFromEnhancedToNonEnhanced(self, mock_check):
- """Verify that we transition correctly to non-enhanced mode.
- Args:
- mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
- method.
- """
- # Set the interrogation mode to always so that we actually interrogate.
- self.console.interrogation_mode = b'always'
-
- # First, assume that the EC interrogations indicate an non-enhanced EC
- # image.
- mock_check.return_value = False
- # But our current knowledge of the EC image (which was actually the
- # 'previous' EC) was an enhanced image.
- self.console.enhanced_ec = True
-
- test_command = b'sysinfo'
- input_stream = []
- input_stream.extend(BytesToByteList(test_command))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # But, we will negotiate to non-enhanced however, dropping this command.
- # Verify this.
- self.assertFalse(self.console.enhanced_ec, msg=('Did not negotiate to'
- 'non-enhanced EC image.'))
- CheckInputBuffer(self, b'')
- CheckInputBufferPosition(self, 0)
-
- # The carriage return should have passed through though.
- expected_calls = []
- expected_calls.append(mock.call(
- six.int2byte(console.ControlKey.CARRIAGE_RETURN)))
-
- # Since the command was dropped, repeat the command.
- input_stream = BytesToByteList(test_command)
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # Since we're not enhanced now, we should have sent each character in the
- # entire command separately and a carriage return.
- for char in test_command:
- if six.PY3:
- expected_calls.append(mock.call(bytes([char])))
- else:
- expected_calls.append(mock.call(char))
- expected_calls.append(mock.call(
- six.int2byte(console.ControlKey.CARRIAGE_RETURN)))
-
- # Verify all of the calls.
- self.console.cmd_pipe.send.assert_has_calls(expected_calls)
-
- def test_EnhancedCheckIfTimedOut(self):
- """Verify that the check returns false if it times out."""
- # Make the debug pipe "time out".
- self.console.dbg_pipe.poll.return_value = False
- self.assertFalse(self.console.CheckForEnhancedECImage())
-
- def test_EnhancedCheckIfACKReceived(self):
- """Verify that the check returns true if the ACK is received."""
- # Make the debug pipe return EC_ACK.
- self.console.dbg_pipe.poll.return_value = True
- self.console.dbg_pipe.recv.return_value = interpreter.EC_ACK
- self.assertTrue(self.console.CheckForEnhancedECImage())
-
- def test_EnhancedCheckIfWrong(self):
- """Verify that the check returns false if byte received is wrong."""
- # Make the debug pipe return the wrong byte.
- self.console.dbg_pipe.poll.return_value = True
- self.console.dbg_pipe.recv.return_value = b'\xff'
- self.assertFalse(self.console.CheckForEnhancedECImage())
-
- def test_EnhancedCheckUsingBuffer(self):
- """Verify that given reboot output, enhanced EC images are detected."""
- enhanced_output_stream = b"""
+class TestConsoleCompatibility(unittest.TestCase):
+ """Verify that console can speak to enhanced and non-enhanced EC images."""
+
+ def setUp(self):
+ """Setup the test harness."""
+ # Setup logging with a timestamp, the module, and the log level.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+ # Create a temp file and set both the controller and peripheral PTYs to the
+ # file to create a loopback.
+ self.tempfile = tempfile.TemporaryFile()
+
+ # Mock out the pipes.
+ mock_pipe_end_0, mock_pipe_end_1 = mock.MagicMock(), mock.MagicMock()
+ self.console = console.Console(
+ self.tempfile.fileno(),
+ self.tempfile,
+ tempfile.TemporaryFile(),
+ mock_pipe_end_0,
+ mock_pipe_end_1,
+ "EC",
+ )
+
+ @mock.patch("ec3po.console.Console.CheckForEnhancedECImage")
+ def test_ActAsPassThruInNonEnhancedMode(self, mock_check):
+ """Verify we simply pass everything thru to non-enhanced ECs.
+
+ Args:
+ mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
+ method.
+ """
+ # Set the interrogation mode to always so that we actually interrogate.
+ self.console.interrogation_mode = b"always"
+
+ # Assume EC interrogations indicate that the image is non-enhanced.
+ mock_check.return_value = False
+
+ # Press enter, followed by the command, and another enter.
+ input_stream = []
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ test_command = b"version"
+ input_stream.extend(BytesToByteList(test_command))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Expected calls to send down the pipe would be each character of the test
+ # command.
+ expected_calls = []
+ expected_calls.append(
+ mock.call(six.int2byte(console.ControlKey.CARRIAGE_RETURN))
+ )
+ for char in test_command:
+ if six.PY3:
+ expected_calls.append(mock.call(bytes([char])))
+ else:
+ expected_calls.append(mock.call(char))
+ expected_calls.append(
+ mock.call(six.int2byte(console.ControlKey.CARRIAGE_RETURN))
+ )
+
+ # Verify that the calls happened.
+ self.console.cmd_pipe.send.assert_has_calls(expected_calls)
+
+ # Since we're acting as a pass-thru, the input buffer should be empty and
+ # input_buffer_pos is 0.
+ CheckInputBuffer(self, b"")
+ CheckInputBufferPosition(self, 0)
+
+ @mock.patch("ec3po.console.Console.CheckForEnhancedECImage")
+ def test_TransitionFromNonEnhancedToEnhanced(self, mock_check):
+ """Verify that we transition correctly to enhanced mode.
+
+ Args:
+ mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
+ method.
+ """
+ # Set the interrogation mode to always so that we actually interrogate.
+ self.console.interrogation_mode = b"always"
+
+ # First, assume that the EC interrogations indicate an enhanced EC image.
+ mock_check.return_value = True
+ # But our current knowledge of the EC image (which was actually the
+ # 'previous' EC) was a non-enhanced image.
+ self.console.enhanced_ec = False
+
+ test_command = b"sysinfo"
+ input_stream = []
+ input_stream.extend(BytesToByteList(test_command))
+
+ expected_calls = []
+ # All keystrokes to the console should be directed straight through to the
+ # EC until we press the enter key.
+ for char in test_command:
+ if six.PY3:
+ expected_calls.append(mock.call(bytes([char])))
+ else:
+ expected_calls.append(mock.call(char))
+
+ # Press the enter key.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ # The enter key should not be sent to the pipe since we should negotiate
+ # to an enhanced EC image.
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # At this point, we should have negotiated to enhanced.
+ self.assertTrue(
+ self.console.enhanced_ec, msg=("Did not negotiate to " "enhanced EC image.")
+ )
+
+ # The command would have been dropped however, so verify this...
+ CheckInputBuffer(self, b"")
+ CheckInputBufferPosition(self, 0)
+ # ...and repeat the command.
+ input_stream = BytesToByteList(test_command)
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Since we're enhanced now, we should have sent the entire command as one
+ # string with no trailing carriage return
+ expected_calls.append(mock.call(test_command))
+
+ # Verify all of the calls.
+ self.console.cmd_pipe.send.assert_has_calls(expected_calls)
+
+ @mock.patch("ec3po.console.Console.CheckForEnhancedECImage")
+ def test_TransitionFromEnhancedToNonEnhanced(self, mock_check):
+ """Verify that we transition correctly to non-enhanced mode.
+
+ Args:
+ mock_check: A MagicMock object replacing the CheckForEnhancedECImage()
+ method.
+ """
+ # Set the interrogation mode to always so that we actually interrogate.
+ self.console.interrogation_mode = b"always"
+
+ # First, assume that the EC interrogations indicate an non-enhanced EC
+ # image.
+ mock_check.return_value = False
+ # But our current knowledge of the EC image (which was actually the
+ # 'previous' EC) was an enhanced image.
+ self.console.enhanced_ec = True
+
+ test_command = b"sysinfo"
+ input_stream = []
+ input_stream.extend(BytesToByteList(test_command))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # But, we will negotiate to non-enhanced however, dropping this command.
+ # Verify this.
+ self.assertFalse(
+ self.console.enhanced_ec,
+ msg=("Did not negotiate to" "non-enhanced EC image."),
+ )
+ CheckInputBuffer(self, b"")
+ CheckInputBufferPosition(self, 0)
+
+ # The carriage return should have passed through though.
+ expected_calls = []
+ expected_calls.append(
+ mock.call(six.int2byte(console.ControlKey.CARRIAGE_RETURN))
+ )
+
+ # Since the command was dropped, repeat the command.
+ input_stream = BytesToByteList(test_command)
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # Since we're not enhanced now, we should have sent each character in the
+ # entire command separately and a carriage return.
+ for char in test_command:
+ if six.PY3:
+ expected_calls.append(mock.call(bytes([char])))
+ else:
+ expected_calls.append(mock.call(char))
+ expected_calls.append(
+ mock.call(six.int2byte(console.ControlKey.CARRIAGE_RETURN))
+ )
+
+ # Verify all of the calls.
+ self.console.cmd_pipe.send.assert_has_calls(expected_calls)
+
+ def test_EnhancedCheckIfTimedOut(self):
+ """Verify that the check returns false if it times out."""
+ # Make the debug pipe "time out".
+ self.console.dbg_pipe.poll.return_value = False
+ self.assertFalse(self.console.CheckForEnhancedECImage())
+
+ def test_EnhancedCheckIfACKReceived(self):
+ """Verify that the check returns true if the ACK is received."""
+ # Make the debug pipe return EC_ACK.
+ self.console.dbg_pipe.poll.return_value = True
+ self.console.dbg_pipe.recv.return_value = interpreter.EC_ACK
+ self.assertTrue(self.console.CheckForEnhancedECImage())
+
+ def test_EnhancedCheckIfWrong(self):
+ """Verify that the check returns false if byte received is wrong."""
+ # Make the debug pipe return the wrong byte.
+ self.console.dbg_pipe.poll.return_value = True
+ self.console.dbg_pipe.recv.return_value = b"\xff"
+ self.assertFalse(self.console.CheckForEnhancedECImage())
+
+ def test_EnhancedCheckUsingBuffer(self):
+ """Verify that given reboot output, enhanced EC images are detected."""
+ enhanced_output_stream = b"""
--- UART initialized after reboot ---
[Reset cause: reset-pin soft]
[Image: RO, jerry_v1.1.4363-2af8572-dirty 2016-02-23 13:26:20 aaboagye@lithium.mtv.corp.google.com]
@@ -1295,19 +1343,19 @@ Enhanced Console is enabled (v1.0.0); type HELP for help.
[0.224060 hash done 41dac382e3a6e3d2ea5b4d789c1bc46525cae7cc5ff6758f0de8d8369b506f57]
[0.375150 POWER_GOOD seen]
"""
- for line in enhanced_output_stream.split(b'\n'):
- self.console.CheckBufferForEnhancedImage(line)
+ for line in enhanced_output_stream.split(b"\n"):
+ self.console.CheckBufferForEnhancedImage(line)
- # Since the enhanced console string was present in the output, the console
- # should have caught it.
- self.assertTrue(self.console.enhanced_ec)
+ # Since the enhanced console string was present in the output, the console
+ # should have caught it.
+ self.assertTrue(self.console.enhanced_ec)
- # Also should check that the command was sent to the interpreter.
- self.console.cmd_pipe.send.assert_called_once_with(b'enhanced True')
+ # Also should check that the command was sent to the interpreter.
+ self.console.cmd_pipe.send.assert_called_once_with(b"enhanced True")
- # Now test the non-enhanced EC image.
- self.console.cmd_pipe.reset_mock()
- non_enhanced_output_stream = b"""
+ # Now test the non-enhanced EC image.
+ self.console.cmd_pipe.reset_mock()
+ non_enhanced_output_stream = b"""
--- UART initialized after reboot ---
[Reset cause: reset-pin soft]
[Image: RO, jerry_v1.1.4363-2af8572-dirty 2016-02-23 13:03:15 aaboagye@lithium.mtv.corp.google.com]
@@ -1331,239 +1379,253 @@ Console is enabled; type HELP for help.
[0.010285 power on 2]
[0.010385 power state 5 = S5->S3, in 0x0000]
"""
- for line in non_enhanced_output_stream.split(b'\n'):
- self.console.CheckBufferForEnhancedImage(line)
+ for line in non_enhanced_output_stream.split(b"\n"):
+ self.console.CheckBufferForEnhancedImage(line)
- # Since the default console string is present in the output, it should be
- # determined to be non enhanced now.
- self.assertFalse(self.console.enhanced_ec)
+ # Since the default console string is present in the output, it should be
+ # determined to be non enhanced now.
+ self.assertFalse(self.console.enhanced_ec)
- # Check that command was also sent to the interpreter.
- self.console.cmd_pipe.send.assert_called_once_with(b'enhanced False')
+ # Check that command was also sent to the interpreter.
+ self.console.cmd_pipe.send.assert_called_once_with(b"enhanced False")
class TestOOBMConsoleCommands(unittest.TestCase):
- """Verify that OOBM console commands work correctly."""
- def setUp(self):
- """Setup the test harness."""
- # Setup logging with a timestamp, the module, and the log level.
- logging.basicConfig(level=logging.DEBUG,
- format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
- # Create a temp file and set both the controller and peripheral PTYs to the
- # file to create a loopback.
- self.tempfile = tempfile.TemporaryFile()
-
- # Mock out the pipes.
- mock_pipe_end_0, mock_pipe_end_1 = mock.MagicMock(), mock.MagicMock()
- self.console = console.Console(self.tempfile.fileno(), self.tempfile,
- tempfile.TemporaryFile(),
- mock_pipe_end_0, mock_pipe_end_1, "EC")
- self.console.oobm_queue = mock.MagicMock()
-
- @mock.patch('ec3po.console.Console.CheckForEnhancedECImage')
- def test_InterrogateCommand(self, mock_check):
- """Verify that 'interrogate' command works as expected.
-
- Args:
- mock_check: A MagicMock object replacing the CheckForEnhancedECIMage()
- method.
- """
- input_stream = []
- expected_calls = []
- mock_check.side_effect = [False]
-
- # 'interrogate never' should disable the interrogation from happening at
- # all.
- cmd = b'interrogate never'
- # Enter the OOBM prompt.
- input_stream.extend(BytesToByteList(b'%'))
- # Type the command
- input_stream.extend(BytesToByteList(cmd))
- # Press enter.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- input_stream = []
-
- # The OOBM queue should have been called with the command being put.
- expected_calls.append(mock.call.put(cmd))
- self.console.oobm_queue.assert_has_calls(expected_calls)
-
- # Process the OOBM queue.
- self.console.oobm_queue.get.side_effect = [cmd]
- self.console.ProcessOOBMQueue()
-
- # Type out a few commands.
- input_stream.extend(BytesToByteList(b'version'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'flashinfo'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'sysinfo'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The Check function should NOT have been called at all.
- mock_check.assert_not_called()
-
- # The EC image should be assumed to be not enhanced.
- self.assertFalse(self.console.enhanced_ec, 'The image should be assumed to'
- ' be NOT enhanced.')
-
- # Reset the mocks.
- mock_check.reset_mock()
- self.console.oobm_queue.reset_mock()
-
- # 'interrogate auto' should not interrogate at all. It should only be
- # scanning the output stream for the 'console is enabled' strings.
- cmd = b'interrogate auto'
- # Enter the OOBM prompt.
- input_stream.extend(BytesToByteList(b'%'))
- # Type the command
- input_stream.extend(BytesToByteList(cmd))
- # Press enter.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- input_stream = []
- expected_calls = []
-
- # The OOBM queue should have been called with the command being put.
- expected_calls.append(mock.call.put(cmd))
- self.console.oobm_queue.assert_has_calls(expected_calls)
-
- # Process the OOBM queue.
- self.console.oobm_queue.get.side_effect = [cmd]
- self.console.ProcessOOBMQueue()
-
- # Type out a few commands.
- input_stream.extend(BytesToByteList(b'version'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'flashinfo'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'sysinfo'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The Check function should NOT have been called at all.
- mock_check.assert_not_called()
-
- # The EC image should be assumed to be not enhanced.
- self.assertFalse(self.console.enhanced_ec, 'The image should be assumed to'
- ' be NOT enhanced.')
-
- # Reset the mocks.
- mock_check.reset_mock()
- self.console.oobm_queue.reset_mock()
-
- # 'interrogate always' should, like its name implies, interrogate always
- # after each press of the enter key. This was the former way of doing
- # interrogation.
- cmd = b'interrogate always'
- # Enter the OOBM prompt.
- input_stream.extend(BytesToByteList(b'%'))
- # Type the command
- input_stream.extend(BytesToByteList(cmd))
- # Press enter.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- input_stream = []
- expected_calls = []
-
- # The OOBM queue should have been called with the command being put.
- expected_calls.append(mock.call.put(cmd))
- self.console.oobm_queue.assert_has_calls(expected_calls)
-
- # Process the OOBM queue.
- self.console.oobm_queue.get.side_effect = [cmd]
- self.console.ProcessOOBMQueue()
-
- # The Check method should be called 3 times here.
- mock_check.side_effect = [False, False, False]
-
- # Type out a few commands.
- input_stream.extend(BytesToByteList(b'help list'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'taskinfo'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'hibdelay'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The Check method should have been called 3 times here.
- expected_calls = [mock.call(), mock.call(), mock.call()]
- mock_check.assert_has_calls(expected_calls)
-
- # The EC image should be assumed to be not enhanced.
- self.assertFalse(self.console.enhanced_ec, 'The image should be assumed to'
- ' be NOT enhanced.')
-
- # Now, let's try to assume that the image is enhanced while still disabling
- # interrogation.
- mock_check.reset_mock()
- self.console.oobm_queue.reset_mock()
- input_stream = []
- cmd = b'interrogate never enhanced'
- # Enter the OOBM prompt.
- input_stream.extend(BytesToByteList(b'%'))
- # Type the command
- input_stream.extend(BytesToByteList(cmd))
- # Press enter.
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- input_stream = []
- expected_calls = []
-
- # The OOBM queue should have been called with the command being put.
- expected_calls.append(mock.call.put(cmd))
- self.console.oobm_queue.assert_has_calls(expected_calls)
-
- # Process the OOBM queue.
- self.console.oobm_queue.get.side_effect = [cmd]
- self.console.ProcessOOBMQueue()
-
- # Type out a few commands.
- input_stream.extend(BytesToByteList(b'chgstate'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'hash'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
- input_stream.extend(BytesToByteList(b'sysjump rw'))
- input_stream.append(console.ControlKey.CARRIAGE_RETURN)
-
- # Send the sequence out.
- for byte in input_stream:
- self.console.HandleChar(byte)
-
- # The check method should have never been called.
- mock_check.assert_not_called()
-
- # The EC image should be assumed to be enhanced.
- self.assertTrue(self.console.enhanced_ec, 'The image should be'
- ' assumed to be enhanced.')
-
-
-if __name__ == '__main__':
- unittest.main()
+ """Verify that OOBM console commands work correctly."""
+
+ def setUp(self):
+ """Setup the test harness."""
+ # Setup logging with a timestamp, the module, and the log level.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+ # Create a temp file and set both the controller and peripheral PTYs to the
+ # file to create a loopback.
+ self.tempfile = tempfile.TemporaryFile()
+
+ # Mock out the pipes.
+ mock_pipe_end_0, mock_pipe_end_1 = mock.MagicMock(), mock.MagicMock()
+ self.console = console.Console(
+ self.tempfile.fileno(),
+ self.tempfile,
+ tempfile.TemporaryFile(),
+ mock_pipe_end_0,
+ mock_pipe_end_1,
+ "EC",
+ )
+ self.console.oobm_queue = mock.MagicMock()
+
+ @mock.patch("ec3po.console.Console.CheckForEnhancedECImage")
+ def test_InterrogateCommand(self, mock_check):
+ """Verify that 'interrogate' command works as expected.
+
+ Args:
+ mock_check: A MagicMock object replacing the CheckForEnhancedECIMage()
+ method.
+ """
+ input_stream = []
+ expected_calls = []
+ mock_check.side_effect = [False]
+
+ # 'interrogate never' should disable the interrogation from happening at
+ # all.
+ cmd = b"interrogate never"
+ # Enter the OOBM prompt.
+ input_stream.extend(BytesToByteList(b"%"))
+ # Type the command
+ input_stream.extend(BytesToByteList(cmd))
+ # Press enter.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ input_stream = []
+
+ # The OOBM queue should have been called with the command being put.
+ expected_calls.append(mock.call.put(cmd))
+ self.console.oobm_queue.assert_has_calls(expected_calls)
+
+ # Process the OOBM queue.
+ self.console.oobm_queue.get.side_effect = [cmd]
+ self.console.ProcessOOBMQueue()
+
+ # Type out a few commands.
+ input_stream.extend(BytesToByteList(b"version"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"flashinfo"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"sysinfo"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The Check function should NOT have been called at all.
+ mock_check.assert_not_called()
+
+ # The EC image should be assumed to be not enhanced.
+ self.assertFalse(
+ self.console.enhanced_ec,
+ "The image should be assumed to" " be NOT enhanced.",
+ )
+
+ # Reset the mocks.
+ mock_check.reset_mock()
+ self.console.oobm_queue.reset_mock()
+
+ # 'interrogate auto' should not interrogate at all. It should only be
+ # scanning the output stream for the 'console is enabled' strings.
+ cmd = b"interrogate auto"
+ # Enter the OOBM prompt.
+ input_stream.extend(BytesToByteList(b"%"))
+ # Type the command
+ input_stream.extend(BytesToByteList(cmd))
+ # Press enter.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ input_stream = []
+ expected_calls = []
+
+ # The OOBM queue should have been called with the command being put.
+ expected_calls.append(mock.call.put(cmd))
+ self.console.oobm_queue.assert_has_calls(expected_calls)
+
+ # Process the OOBM queue.
+ self.console.oobm_queue.get.side_effect = [cmd]
+ self.console.ProcessOOBMQueue()
+
+ # Type out a few commands.
+ input_stream.extend(BytesToByteList(b"version"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"flashinfo"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"sysinfo"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The Check function should NOT have been called at all.
+ mock_check.assert_not_called()
+
+ # The EC image should be assumed to be not enhanced.
+ self.assertFalse(
+ self.console.enhanced_ec,
+ "The image should be assumed to" " be NOT enhanced.",
+ )
+
+ # Reset the mocks.
+ mock_check.reset_mock()
+ self.console.oobm_queue.reset_mock()
+
+ # 'interrogate always' should, like its name implies, interrogate always
+ # after each press of the enter key. This was the former way of doing
+ # interrogation.
+ cmd = b"interrogate always"
+ # Enter the OOBM prompt.
+ input_stream.extend(BytesToByteList(b"%"))
+ # Type the command
+ input_stream.extend(BytesToByteList(cmd))
+ # Press enter.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ input_stream = []
+ expected_calls = []
+
+ # The OOBM queue should have been called with the command being put.
+ expected_calls.append(mock.call.put(cmd))
+ self.console.oobm_queue.assert_has_calls(expected_calls)
+
+ # Process the OOBM queue.
+ self.console.oobm_queue.get.side_effect = [cmd]
+ self.console.ProcessOOBMQueue()
+
+ # The Check method should be called 3 times here.
+ mock_check.side_effect = [False, False, False]
+
+ # Type out a few commands.
+ input_stream.extend(BytesToByteList(b"help list"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"taskinfo"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"hibdelay"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The Check method should have been called 3 times here.
+ expected_calls = [mock.call(), mock.call(), mock.call()]
+ mock_check.assert_has_calls(expected_calls)
+
+ # The EC image should be assumed to be not enhanced.
+ self.assertFalse(
+ self.console.enhanced_ec,
+ "The image should be assumed to" " be NOT enhanced.",
+ )
+
+ # Now, let's try to assume that the image is enhanced while still disabling
+ # interrogation.
+ mock_check.reset_mock()
+ self.console.oobm_queue.reset_mock()
+ input_stream = []
+ cmd = b"interrogate never enhanced"
+ # Enter the OOBM prompt.
+ input_stream.extend(BytesToByteList(b"%"))
+ # Type the command
+ input_stream.extend(BytesToByteList(cmd))
+ # Press enter.
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ input_stream = []
+ expected_calls = []
+
+ # The OOBM queue should have been called with the command being put.
+ expected_calls.append(mock.call.put(cmd))
+ self.console.oobm_queue.assert_has_calls(expected_calls)
+
+ # Process the OOBM queue.
+ self.console.oobm_queue.get.side_effect = [cmd]
+ self.console.ProcessOOBMQueue()
+
+ # Type out a few commands.
+ input_stream.extend(BytesToByteList(b"chgstate"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"hash"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+ input_stream.extend(BytesToByteList(b"sysjump rw"))
+ input_stream.append(console.ControlKey.CARRIAGE_RETURN)
+
+ # Send the sequence out.
+ for byte in input_stream:
+ self.console.HandleChar(byte)
+
+ # The check method should have never been called.
+ mock_check.assert_not_called()
+
+ # The EC image should be assumed to be enhanced.
+ self.assertTrue(
+ self.console.enhanced_ec, "The image should be" " assumed to be enhanced."
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/util/ec3po/interpreter.py b/util/ec3po/interpreter.py
index 4e151083bd..591603e038 100644
--- a/util/ec3po/interpreter.py
+++ b/util/ec3po/interpreter.py
@@ -25,443 +25,447 @@ import traceback
import six
-
COMMAND_RETRIES = 3 # Number of attempts to retry a command.
EC_MAX_READ = 1024 # Max bytes to read at a time from the EC.
-EC_SYN = b'\xec' # Byte indicating EC interrogation.
-EC_ACK = b'\xc0' # Byte representing correct EC response to interrogation.
+EC_SYN = b"\xec" # Byte indicating EC interrogation.
+EC_ACK = b"\xc0" # Byte representing correct EC response to interrogation.
class LoggerAdapter(logging.LoggerAdapter):
- """Class which provides a small adapter for the logger."""
+ """Class which provides a small adapter for the logger."""
- def process(self, msg, kwargs):
- """Prepends the served PTY to the beginning of the log message."""
- return '%s - %s' % (self.extra['pty'], msg), kwargs
+ def process(self, msg, kwargs):
+ """Prepends the served PTY to the beginning of the log message."""
+ return "%s - %s" % (self.extra["pty"], msg), kwargs
class Interpreter(object):
- """Class which provides the interpretation layer between the EC and user.
-
- This class essentially performs all of the intepretation for the EC and the
- user. It handles all of the automatic command retrying as well as the
- formation of commands for EC images which support that.
-
- Attributes:
- logger: A logger for this module.
- ec_uart_pty: An opened file object to the raw EC UART PTY.
- ec_uart_pty_name: A string containing the name of the raw EC UART PTY.
- cmd_pipe: A socket.socket or multiprocessing.Connection object which
- represents the Interpreter side of the command pipe. This must be a
- bidirectional pipe. Commands and responses will utilize this pipe.
- dbg_pipe: A socket.socket or multiprocessing.Connection object which
- represents the Interpreter side of the debug pipe. This must be a
- unidirectional pipe with write capabilities. EC debug output will utilize
- this pipe.
- cmd_retries: An integer representing the number of attempts the console
- should retry commands if it receives an error.
- log_level: An integer representing the numeric value of the log level.
- inputs: A list of objects that the intpreter selects for reading.
- Initially, these are the EC UART and the command pipe.
- outputs: A list of objects that the interpreter selects for writing.
- ec_cmd_queue: A FIFO queue used for sending commands down to the EC UART.
- last_cmd: A string that represents the last command sent to the EC. If an
- error is encountered, the interpreter will attempt to retry this command
- up to COMMAND_RETRIES.
- enhanced_ec: A boolean indicating if the EC image that we are currently
- communicating with is enhanced or not. Enhanced EC images will support
- packed commands and host commands over the UART. This defaults to False
- and is changed depending on the result of an interrogation.
- interrogating: A boolean indicating if we are in the middle of interrogating
- the EC.
- connected: A boolean indicating if the interpreter is actually connected to
- the UART and listening.
- """
- def __init__(self, ec_uart_pty, cmd_pipe, dbg_pipe, log_level=logging.INFO,
- name=None):
- """Intializes an Interpreter object with the provided args.
+ """Class which provides the interpretation layer between the EC and user.
- Args:
- ec_uart_pty: A string representing the EC UART to connect to.
+ This class essentially performs all of the intepretation for the EC and the
+ user. It handles all of the automatic command retrying as well as the
+ formation of commands for EC images which support that.
+
+ Attributes:
+ logger: A logger for this module.
+ ec_uart_pty: An opened file object to the raw EC UART PTY.
+ ec_uart_pty_name: A string containing the name of the raw EC UART PTY.
cmd_pipe: A socket.socket or multiprocessing.Connection object which
represents the Interpreter side of the command pipe. This must be a
bidirectional pipe. Commands and responses will utilize this pipe.
dbg_pipe: A socket.socket or multiprocessing.Connection object which
represents the Interpreter side of the debug pipe. This must be a
- unidirectional pipe with write capabilities. EC debug output will
- utilize this pipe.
+ unidirectional pipe with write capabilities. EC debug output will utilize
+ this pipe.
cmd_retries: An integer representing the number of attempts the console
should retry commands if it receives an error.
- log_level: An optional integer representing the numeric value of the log
- level. By default, the log level will be logging.INFO (20).
- name: the console source name
- """
- # Create a unique logger based on the interpreter name
- interpreter_prefix = ('%s - ' % name) if name else ''
- logger = logging.getLogger('%sEC3PO.Interpreter' % interpreter_prefix)
- self.logger = LoggerAdapter(logger, {'pty': ec_uart_pty})
- # TODO(https://crbug.com/1162189): revist the 2 TODOs below
- # TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
- # TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
- # that gets fixed, or keep two pty: one for reading and one for writing
- self.ec_uart_pty = open(ec_uart_pty, 'r+b', buffering=0)
- self.ec_uart_pty_name = ec_uart_pty
- self.cmd_pipe = cmd_pipe
- self.dbg_pipe = dbg_pipe
- self.cmd_retries = COMMAND_RETRIES
- self.log_level = log_level
- self.inputs = [self.ec_uart_pty, self.cmd_pipe]
- self.outputs = []
- self.ec_cmd_queue = six.moves.queue.Queue()
- self.last_cmd = b''
- self.enhanced_ec = False
- self.interrogating = False
- self.connected = True
-
- def __str__(self):
- """Show internal state of the Interpreter object.
-
- Returns:
- A string that shows the values of the attributes.
- """
- string = []
- string.append('%r' % self)
- string.append('ec_uart_pty: %s' % self.ec_uart_pty)
- string.append('cmd_pipe: %r' % self.cmd_pipe)
- string.append('dbg_pipe: %r' % self.dbg_pipe)
- string.append('cmd_retries: %d' % self.cmd_retries)
- string.append('log_level: %d' % self.log_level)
- string.append('inputs: %r' % self.inputs)
- string.append('outputs: %r' % self.outputs)
- string.append('ec_cmd_queue: %r' % self.ec_cmd_queue)
- string.append('last_cmd: \'%s\'' % self.last_cmd)
- string.append('enhanced_ec: %r' % self.enhanced_ec)
- string.append('interrogating: %r' % self.interrogating)
- return '\n'.join(string)
-
- def EnqueueCmd(self, command):
- """Enqueue a command to be sent to the EC UART.
-
- Args:
- command: A string which contains the command to be sent.
+ log_level: An integer representing the numeric value of the log level.
+ inputs: A list of objects that the intpreter selects for reading.
+ Initially, these are the EC UART and the command pipe.
+ outputs: A list of objects that the interpreter selects for writing.
+ ec_cmd_queue: A FIFO queue used for sending commands down to the EC UART.
+ last_cmd: A string that represents the last command sent to the EC. If an
+ error is encountered, the interpreter will attempt to retry this command
+ up to COMMAND_RETRIES.
+ enhanced_ec: A boolean indicating if the EC image that we are currently
+ communicating with is enhanced or not. Enhanced EC images will support
+ packed commands and host commands over the UART. This defaults to False
+ and is changed depending on the result of an interrogation.
+ interrogating: A boolean indicating if we are in the middle of interrogating
+ the EC.
+ connected: A boolean indicating if the interpreter is actually connected to
+ the UART and listening.
"""
- self.ec_cmd_queue.put(command)
- self.logger.log(1, 'Commands now in queue: %d', self.ec_cmd_queue.qsize())
- # Add the EC UART as an output to be serviced.
- if self.connected and self.ec_uart_pty not in self.outputs:
- self.outputs.append(self.ec_uart_pty)
-
- def PackCommand(self, raw_cmd):
- r"""Packs a command for use with error checking.
-
- For error checking, we pack console commands in a particular format. The
- format is as follows:
-
- &&[x][x][x][x]&{cmd}\n\n
- ^ ^ ^^ ^^ ^ ^-- 2 newlines.
- | | || || |-- the raw console command.
- | | || ||-- 1 ampersand.
- | | ||____|--- 2 hex digits representing the CRC8 of cmd.
- | |____|-- 2 hex digits reprsenting the length of cmd.
- |-- 2 ampersands
-
- Args:
- raw_cmd: A pre-packed string which contains the raw command.
-
- Returns:
- A string which contains the packed command.
- """
- # Don't pack a single carriage return.
- if raw_cmd != b'\r':
- # The command format is as follows.
- # &&[x][x][x][x]&{cmd}\n\n
- packed_cmd = []
- packed_cmd.append(b'&&')
- # The first pair of hex digits are the length of the command.
- packed_cmd.append(b'%02x' % len(raw_cmd))
- # Then the CRC8 of cmd.
- packed_cmd.append(b'%02x' % Crc8(raw_cmd))
- packed_cmd.append(b'&')
- # Now, the raw command followed by 2 newlines.
- packed_cmd.append(raw_cmd)
- packed_cmd.append(b'\n\n')
- return b''.join(packed_cmd)
- else:
- return raw_cmd
-
- def ProcessCommand(self, command):
- """Captures the input determines what actions to take.
-
- Args:
- command: A string representing the command sent by the user.
- """
- if command == b'disconnect':
- if self.connected:
- self.logger.debug('UART disconnect request.')
- # Drop all pending commands if any.
- while not self.ec_cmd_queue.empty():
- c = self.ec_cmd_queue.get()
- self.logger.debug('dropped: \'%s\'', c)
- if self.enhanced_ec:
- # Reset retry state.
- self.cmd_retries = COMMAND_RETRIES
- self.last_cmd = b''
- # Get the UART that the interpreter is attached to.
- fileobj = self.ec_uart_pty
- self.logger.debug('fileobj: %r', fileobj)
- # Remove the descriptor from the inputs and outputs.
- self.inputs.remove(fileobj)
- if fileobj in self.outputs:
- self.outputs.remove(fileobj)
- self.logger.debug('Removed fileobj. Remaining inputs: %r', self.inputs)
- # Close the file.
- fileobj.close()
- # Mark the interpreter as disconnected now.
- self.connected = False
- self.logger.debug('Disconnected from %s.', self.ec_uart_pty_name)
- return
-
- elif command == b'reconnect':
- if not self.connected:
- self.logger.debug('UART reconnect request.')
- # Reopen the PTY.
+ def __init__(
+ self, ec_uart_pty, cmd_pipe, dbg_pipe, log_level=logging.INFO, name=None
+ ):
+ """Intializes an Interpreter object with the provided args.
+
+ Args:
+ ec_uart_pty: A string representing the EC UART to connect to.
+ cmd_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the Interpreter side of the command pipe. This must be a
+ bidirectional pipe. Commands and responses will utilize this pipe.
+ dbg_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the Interpreter side of the debug pipe. This must be a
+ unidirectional pipe with write capabilities. EC debug output will
+ utilize this pipe.
+ cmd_retries: An integer representing the number of attempts the console
+ should retry commands if it receives an error.
+ log_level: An optional integer representing the numeric value of the log
+ level. By default, the log level will be logging.INFO (20).
+ name: the console source name
+ """
+ # Create a unique logger based on the interpreter name
+ interpreter_prefix = ("%s - " % name) if name else ""
+ logger = logging.getLogger("%sEC3PO.Interpreter" % interpreter_prefix)
+ self.logger = LoggerAdapter(logger, {"pty": ec_uart_pty})
+ # TODO(https://crbug.com/1162189): revist the 2 TODOs below
# TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
# TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
# that gets fixed, or keep two pty: one for reading and one for writing
- fileobj = open(self.ec_uart_pty_name, 'r+b', buffering=0)
- self.logger.debug('fileobj: %r', fileobj)
- self.ec_uart_pty = fileobj
- # Add the descriptor to the inputs.
- self.inputs.append(fileobj)
- self.logger.debug('fileobj added. curr inputs: %r', self.inputs)
- # Mark the interpreter as connected now.
- self.connected = True
- self.logger.debug('Connected to %s.', self.ec_uart_pty_name)
- return
-
- elif command.startswith(b'enhanced'):
- self.enhanced_ec = command.split(b' ')[1] == b'True'
- return
-
- # Ignore any other commands while in the disconnected state.
- self.logger.log(1, 'command: \'%s\'', command)
- if not self.connected:
- self.logger.debug('Ignoring command because currently disconnected.')
- return
-
- # Remove leading and trailing spaces only if this is an enhanced EC image.
- # For non-enhanced EC images, commands will be single characters at a time
- # and can be spaces.
- if self.enhanced_ec:
- command = command.strip(b' ')
-
- # There's nothing to do if the command is empty.
- if len(command) == 0:
- return
-
- # Handle log level change requests.
- if command.startswith(b'loglevel'):
- self.logger.debug('Log level change request.')
- new_log_level = int(command.split(b' ')[1])
- self.logger.logger.setLevel(new_log_level)
- self.logger.info('Log level changed to %d.', new_log_level)
- return
-
- # Check for interrogation command.
- if command == EC_SYN:
- # User is requesting interrogation. Send SYN as is.
- self.logger.debug('User requesting interrogation.')
- self.interrogating = True
- # Assume the EC isn't enhanced until we get a response.
- self.enhanced_ec = False
- elif self.enhanced_ec:
- # Enhanced EC images require the plaintext commands to be packed.
- command = self.PackCommand(command)
- # TODO(aaboagye): Make a dict of commands and keys and eventually,
- # handle partial matching based on unique prefixes.
-
- self.EnqueueCmd(command)
-
- def HandleCmdRetries(self):
- """Attempts to retry commands if possible."""
- if self.cmd_retries > 0:
- # The EC encountered an error. We'll have to retry again.
- self.logger.warning('Retrying command...')
- self.cmd_retries -= 1
- self.logger.warning('Retries remaining: %d', self.cmd_retries)
- # Retry the command and add the EC UART to the writers again.
- self.EnqueueCmd(self.last_cmd)
- self.outputs.append(self.ec_uart_pty)
- else:
- # We're out of retries, so just give up.
- self.logger.error('Command failed. No retries left.')
- # Clear the command in progress.
- self.last_cmd = b''
- # Reset the retry count.
- self.cmd_retries = COMMAND_RETRIES
-
- def SendCmdToEC(self):
- """Sends a command to the EC."""
- # If we're retrying a command, just try to send it again.
- if self.cmd_retries < COMMAND_RETRIES:
- cmd = self.last_cmd
- else:
- # If we're not retrying, we should not be writing to the EC if we have no
- # items in our command queue.
- assert not self.ec_cmd_queue.empty()
- # Get the command to send.
- cmd = self.ec_cmd_queue.get()
-
- # Send the command.
- self.ec_uart_pty.write(cmd)
- self.ec_uart_pty.flush()
- self.logger.log(1, 'Sent command to EC.')
-
- if self.enhanced_ec and cmd != EC_SYN:
- # Now, that we've sent the command, store the current command as the last
- # command sent. If we encounter an error string, we will attempt to retry
- # this command.
- if cmd != self.last_cmd:
- self.last_cmd = cmd
- # Reset the retry count.
+ self.ec_uart_pty = open(ec_uart_pty, "r+b", buffering=0)
+ self.ec_uart_pty_name = ec_uart_pty
+ self.cmd_pipe = cmd_pipe
+ self.dbg_pipe = dbg_pipe
self.cmd_retries = COMMAND_RETRIES
+ self.log_level = log_level
+ self.inputs = [self.ec_uart_pty, self.cmd_pipe]
+ self.outputs = []
+ self.ec_cmd_queue = six.moves.queue.Queue()
+ self.last_cmd = b""
+ self.enhanced_ec = False
+ self.interrogating = False
+ self.connected = True
- # If no command is pending to be sent, then we can remove the EC UART from
- # writers. Might need better checking for command retry logic in here.
- if self.ec_cmd_queue.empty():
- # Remove the EC UART from the writers while we wait for a response.
- self.logger.debug('Removing EC UART from writers.')
- self.outputs.remove(self.ec_uart_pty)
-
- def HandleECData(self):
- """Handle any debug prints from the EC."""
- self.logger.log(1, 'EC has data')
- # Read what the EC sent us.
- data = os.read(self.ec_uart_pty.fileno(), EC_MAX_READ)
- self.logger.log(1, 'got: \'%s\'', binascii.hexlify(data))
- if b'&E' in data and self.enhanced_ec:
- # We received an error, so we should retry it if possible.
- self.logger.warning('Error string found in data.')
- self.HandleCmdRetries()
- return
-
- # If we were interrogating, check the response and update our knowledge
- # of the current EC image.
- if self.interrogating:
- self.enhanced_ec = data == EC_ACK
- if self.enhanced_ec:
- self.logger.debug('The current EC image seems enhanced.')
- else:
- self.logger.debug('The current EC image does NOT seem enhanced.')
- # Done interrogating.
- self.interrogating = False
- # For now, just forward everything the EC sends us.
- self.logger.log(1, 'Forwarding to user...')
- self.dbg_pipe.send(data)
-
- def HandleUserData(self):
- """Handle any incoming commands from the user.
-
- Raises:
- EOFError: Allowed to propagate through from self.cmd_pipe.recv().
- """
- self.logger.log(1, 'Command data available. Begin processing.')
- data = self.cmd_pipe.recv()
- # Process the command.
- self.ProcessCommand(data)
+ def __str__(self):
+ """Show internal state of the Interpreter object.
+
+ Returns:
+ A string that shows the values of the attributes.
+ """
+ string = []
+ string.append("%r" % self)
+ string.append("ec_uart_pty: %s" % self.ec_uart_pty)
+ string.append("cmd_pipe: %r" % self.cmd_pipe)
+ string.append("dbg_pipe: %r" % self.dbg_pipe)
+ string.append("cmd_retries: %d" % self.cmd_retries)
+ string.append("log_level: %d" % self.log_level)
+ string.append("inputs: %r" % self.inputs)
+ string.append("outputs: %r" % self.outputs)
+ string.append("ec_cmd_queue: %r" % self.ec_cmd_queue)
+ string.append("last_cmd: '%s'" % self.last_cmd)
+ string.append("enhanced_ec: %r" % self.enhanced_ec)
+ string.append("interrogating: %r" % self.interrogating)
+ return "\n".join(string)
+
+ def EnqueueCmd(self, command):
+ """Enqueue a command to be sent to the EC UART.
+
+ Args:
+ command: A string which contains the command to be sent.
+ """
+ self.ec_cmd_queue.put(command)
+ self.logger.log(1, "Commands now in queue: %d", self.ec_cmd_queue.qsize())
+
+ # Add the EC UART as an output to be serviced.
+ if self.connected and self.ec_uart_pty not in self.outputs:
+ self.outputs.append(self.ec_uart_pty)
+
+ def PackCommand(self, raw_cmd):
+ r"""Packs a command for use with error checking.
+
+ For error checking, we pack console commands in a particular format. The
+ format is as follows:
+
+ &&[x][x][x][x]&{cmd}\n\n
+ ^ ^ ^^ ^^ ^ ^-- 2 newlines.
+ | | || || |-- the raw console command.
+ | | || ||-- 1 ampersand.
+ | | ||____|--- 2 hex digits representing the CRC8 of cmd.
+ | |____|-- 2 hex digits reprsenting the length of cmd.
+ |-- 2 ampersands
+
+ Args:
+ raw_cmd: A pre-packed string which contains the raw command.
+
+ Returns:
+ A string which contains the packed command.
+ """
+ # Don't pack a single carriage return.
+ if raw_cmd != b"\r":
+ # The command format is as follows.
+ # &&[x][x][x][x]&{cmd}\n\n
+ packed_cmd = []
+ packed_cmd.append(b"&&")
+ # The first pair of hex digits are the length of the command.
+ packed_cmd.append(b"%02x" % len(raw_cmd))
+ # Then the CRC8 of cmd.
+ packed_cmd.append(b"%02x" % Crc8(raw_cmd))
+ packed_cmd.append(b"&")
+ # Now, the raw command followed by 2 newlines.
+ packed_cmd.append(raw_cmd)
+ packed_cmd.append(b"\n\n")
+ return b"".join(packed_cmd)
+ else:
+ return raw_cmd
+
+ def ProcessCommand(self, command):
+ """Captures the input determines what actions to take.
+
+ Args:
+ command: A string representing the command sent by the user.
+ """
+ if command == b"disconnect":
+ if self.connected:
+ self.logger.debug("UART disconnect request.")
+ # Drop all pending commands if any.
+ while not self.ec_cmd_queue.empty():
+ c = self.ec_cmd_queue.get()
+ self.logger.debug("dropped: '%s'", c)
+ if self.enhanced_ec:
+ # Reset retry state.
+ self.cmd_retries = COMMAND_RETRIES
+ self.last_cmd = b""
+ # Get the UART that the interpreter is attached to.
+ fileobj = self.ec_uart_pty
+ self.logger.debug("fileobj: %r", fileobj)
+ # Remove the descriptor from the inputs and outputs.
+ self.inputs.remove(fileobj)
+ if fileobj in self.outputs:
+ self.outputs.remove(fileobj)
+ self.logger.debug("Removed fileobj. Remaining inputs: %r", self.inputs)
+ # Close the file.
+ fileobj.close()
+ # Mark the interpreter as disconnected now.
+ self.connected = False
+ self.logger.debug("Disconnected from %s.", self.ec_uart_pty_name)
+ return
+
+ elif command == b"reconnect":
+ if not self.connected:
+ self.logger.debug("UART reconnect request.")
+ # Reopen the PTY.
+ # TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
+ # TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
+ # that gets fixed, or keep two pty: one for reading and one for writing
+ fileobj = open(self.ec_uart_pty_name, "r+b", buffering=0)
+ self.logger.debug("fileobj: %r", fileobj)
+ self.ec_uart_pty = fileobj
+ # Add the descriptor to the inputs.
+ self.inputs.append(fileobj)
+ self.logger.debug("fileobj added. curr inputs: %r", self.inputs)
+ # Mark the interpreter as connected now.
+ self.connected = True
+ self.logger.debug("Connected to %s.", self.ec_uart_pty_name)
+ return
+
+ elif command.startswith(b"enhanced"):
+ self.enhanced_ec = command.split(b" ")[1] == b"True"
+ return
+
+ # Ignore any other commands while in the disconnected state.
+ self.logger.log(1, "command: '%s'", command)
+ if not self.connected:
+ self.logger.debug("Ignoring command because currently disconnected.")
+ return
+
+ # Remove leading and trailing spaces only if this is an enhanced EC image.
+ # For non-enhanced EC images, commands will be single characters at a time
+ # and can be spaces.
+ if self.enhanced_ec:
+ command = command.strip(b" ")
+
+ # There's nothing to do if the command is empty.
+ if len(command) == 0:
+ return
+
+ # Handle log level change requests.
+ if command.startswith(b"loglevel"):
+ self.logger.debug("Log level change request.")
+ new_log_level = int(command.split(b" ")[1])
+ self.logger.logger.setLevel(new_log_level)
+ self.logger.info("Log level changed to %d.", new_log_level)
+ return
+
+ # Check for interrogation command.
+ if command == EC_SYN:
+ # User is requesting interrogation. Send SYN as is.
+ self.logger.debug("User requesting interrogation.")
+ self.interrogating = True
+ # Assume the EC isn't enhanced until we get a response.
+ self.enhanced_ec = False
+ elif self.enhanced_ec:
+ # Enhanced EC images require the plaintext commands to be packed.
+ command = self.PackCommand(command)
+ # TODO(aaboagye): Make a dict of commands and keys and eventually,
+ # handle partial matching based on unique prefixes.
+
+ self.EnqueueCmd(command)
+
+ def HandleCmdRetries(self):
+ """Attempts to retry commands if possible."""
+ if self.cmd_retries > 0:
+ # The EC encountered an error. We'll have to retry again.
+ self.logger.warning("Retrying command...")
+ self.cmd_retries -= 1
+ self.logger.warning("Retries remaining: %d", self.cmd_retries)
+ # Retry the command and add the EC UART to the writers again.
+ self.EnqueueCmd(self.last_cmd)
+ self.outputs.append(self.ec_uart_pty)
+ else:
+ # We're out of retries, so just give up.
+ self.logger.error("Command failed. No retries left.")
+ # Clear the command in progress.
+ self.last_cmd = b""
+ # Reset the retry count.
+ self.cmd_retries = COMMAND_RETRIES
+
+ def SendCmdToEC(self):
+ """Sends a command to the EC."""
+ # If we're retrying a command, just try to send it again.
+ if self.cmd_retries < COMMAND_RETRIES:
+ cmd = self.last_cmd
+ else:
+ # If we're not retrying, we should not be writing to the EC if we have no
+ # items in our command queue.
+ assert not self.ec_cmd_queue.empty()
+ # Get the command to send.
+ cmd = self.ec_cmd_queue.get()
+
+ # Send the command.
+ self.ec_uart_pty.write(cmd)
+ self.ec_uart_pty.flush()
+ self.logger.log(1, "Sent command to EC.")
+
+ if self.enhanced_ec and cmd != EC_SYN:
+ # Now, that we've sent the command, store the current command as the last
+ # command sent. If we encounter an error string, we will attempt to retry
+ # this command.
+ if cmd != self.last_cmd:
+ self.last_cmd = cmd
+ # Reset the retry count.
+ self.cmd_retries = COMMAND_RETRIES
+
+ # If no command is pending to be sent, then we can remove the EC UART from
+ # writers. Might need better checking for command retry logic in here.
+ if self.ec_cmd_queue.empty():
+ # Remove the EC UART from the writers while we wait for a response.
+ self.logger.debug("Removing EC UART from writers.")
+ self.outputs.remove(self.ec_uart_pty)
+
+ def HandleECData(self):
+ """Handle any debug prints from the EC."""
+ self.logger.log(1, "EC has data")
+ # Read what the EC sent us.
+ data = os.read(self.ec_uart_pty.fileno(), EC_MAX_READ)
+ self.logger.log(1, "got: '%s'", binascii.hexlify(data))
+ if b"&E" in data and self.enhanced_ec:
+ # We received an error, so we should retry it if possible.
+ self.logger.warning("Error string found in data.")
+ self.HandleCmdRetries()
+ return
+
+ # If we were interrogating, check the response and update our knowledge
+ # of the current EC image.
+ if self.interrogating:
+ self.enhanced_ec = data == EC_ACK
+ if self.enhanced_ec:
+ self.logger.debug("The current EC image seems enhanced.")
+ else:
+ self.logger.debug("The current EC image does NOT seem enhanced.")
+ # Done interrogating.
+ self.interrogating = False
+ # For now, just forward everything the EC sends us.
+ self.logger.log(1, "Forwarding to user...")
+ self.dbg_pipe.send(data)
+
+ def HandleUserData(self):
+ """Handle any incoming commands from the user.
+
+ Raises:
+ EOFError: Allowed to propagate through from self.cmd_pipe.recv().
+ """
+ self.logger.log(1, "Command data available. Begin processing.")
+ data = self.cmd_pipe.recv()
+ # Process the command.
+ self.ProcessCommand(data)
def Crc8(data):
- """Calculates the CRC8 of data.
+ """Calculates the CRC8 of data.
- The generator polynomial used is: x^8 + x^2 + x + 1.
- This is the same implementation that is used in the EC.
+ The generator polynomial used is: x^8 + x^2 + x + 1.
+ This is the same implementation that is used in the EC.
- Args:
- data: A string of data that we wish to calculate the CRC8 on.
+ Args:
+ data: A string of data that we wish to calculate the CRC8 on.
- Returns:
- crc >> 8: An integer representing the CRC8 value.
- """
- crc = 0
- for byte in six.iterbytes(data):
- crc ^= (byte << 8)
- for _ in range(8):
- if crc & 0x8000:
- crc ^= (0x1070 << 3)
- crc <<= 1
- return crc >> 8
+ Returns:
+ crc >> 8: An integer representing the CRC8 value.
+ """
+ crc = 0
+ for byte in six.iterbytes(data):
+ crc ^= byte << 8
+ for _ in range(8):
+ if crc & 0x8000:
+ crc ^= 0x1070 << 3
+ crc <<= 1
+ return crc >> 8
def StartLoop(interp, shutdown_pipe=None):
- """Starts an infinite loop of servicing the user and the EC.
-
- StartLoop checks to see if there are any commands to process, processing them
- if any, and forwards EC output to the user.
-
- When sending a command to the EC, we send the command once and check the
- response to see if the EC encountered an error when receiving the command. An
- error condition is reported to the interpreter by a string with at least one
- '&' and 'E'. The full string is actually '&&EE', however it's possible that
- the leading ampersand or trailing 'E' could be dropped. If an error is
- encountered, the interpreter will retry up to the amount configured.
-
- Args:
- interp: An Interpreter object that has been properly initialised.
- shutdown_pipe: A file object for a pipe or equivalent that becomes readable
- (not blocked) to indicate that the loop should exit. Can be None to never
- exit the loop.
- """
- try:
- # This is used instead of "break" to avoid exiting the loop in the middle of
- # an iteration.
- continue_looping = True
-
- while continue_looping:
- # The inputs list is created anew in each loop iteration because the
- # Interpreter class sometimes modifies the interp.inputs list.
- if shutdown_pipe is None:
- inputs = interp.inputs
- else:
- inputs = list(interp.inputs)
- inputs.append(shutdown_pipe)
-
- readable, writeable, _ = select.select(inputs, interp.outputs, [])
-
- for obj in readable:
- # Handle any debug prints from the EC.
- if obj is interp.ec_uart_pty:
- interp.HandleECData()
-
- # Handle any commands from the user.
- elif obj is interp.cmd_pipe:
- try:
- interp.HandleUserData()
- except EOFError:
- interp.logger.debug(
- 'ec3po interpreter received EOF from cmd_pipe in '
- 'HandleUserData()')
- continue_looping = False
-
- elif obj is shutdown_pipe:
- interp.logger.debug(
- 'ec3po interpreter received shutdown pipe unblocked notification')
- continue_looping = False
-
- for obj in writeable:
- # Send a command to the EC.
- if obj is interp.ec_uart_pty:
- interp.SendCmdToEC()
-
- except KeyboardInterrupt:
- pass
-
- finally:
- interp.cmd_pipe.close()
- interp.dbg_pipe.close()
- interp.ec_uart_pty.close()
- if shutdown_pipe is not None:
- shutdown_pipe.close()
- interp.logger.debug('Exit ec3po interpreter loop for %s',
- interp.ec_uart_pty_name)
+ """Starts an infinite loop of servicing the user and the EC.
+
+ StartLoop checks to see if there are any commands to process, processing them
+ if any, and forwards EC output to the user.
+
+ When sending a command to the EC, we send the command once and check the
+ response to see if the EC encountered an error when receiving the command. An
+ error condition is reported to the interpreter by a string with at least one
+ '&' and 'E'. The full string is actually '&&EE', however it's possible that
+ the leading ampersand or trailing 'E' could be dropped. If an error is
+ encountered, the interpreter will retry up to the amount configured.
+
+ Args:
+ interp: An Interpreter object that has been properly initialised.
+ shutdown_pipe: A file object for a pipe or equivalent that becomes readable
+ (not blocked) to indicate that the loop should exit. Can be None to never
+ exit the loop.
+ """
+ try:
+ # This is used instead of "break" to avoid exiting the loop in the middle of
+ # an iteration.
+ continue_looping = True
+
+ while continue_looping:
+ # The inputs list is created anew in each loop iteration because the
+ # Interpreter class sometimes modifies the interp.inputs list.
+ if shutdown_pipe is None:
+ inputs = interp.inputs
+ else:
+ inputs = list(interp.inputs)
+ inputs.append(shutdown_pipe)
+
+ readable, writeable, _ = select.select(inputs, interp.outputs, [])
+
+ for obj in readable:
+ # Handle any debug prints from the EC.
+ if obj is interp.ec_uart_pty:
+ interp.HandleECData()
+
+ # Handle any commands from the user.
+ elif obj is interp.cmd_pipe:
+ try:
+ interp.HandleUserData()
+ except EOFError:
+ interp.logger.debug(
+ "ec3po interpreter received EOF from cmd_pipe in "
+ "HandleUserData()"
+ )
+ continue_looping = False
+
+ elif obj is shutdown_pipe:
+ interp.logger.debug(
+ "ec3po interpreter received shutdown pipe unblocked notification"
+ )
+ continue_looping = False
+
+ for obj in writeable:
+ # Send a command to the EC.
+ if obj is interp.ec_uart_pty:
+ interp.SendCmdToEC()
+
+ except KeyboardInterrupt:
+ pass
+
+ finally:
+ interp.cmd_pipe.close()
+ interp.dbg_pipe.close()
+ interp.ec_uart_pty.close()
+ if shutdown_pipe is not None:
+ shutdown_pipe.close()
+ interp.logger.debug(
+ "Exit ec3po interpreter loop for %s", interp.ec_uart_pty_name
+ )
diff --git a/util/ec3po/interpreter_unittest.py b/util/ec3po/interpreter_unittest.py
index fe4d43c351..509b90f667 100755
--- a/util/ec3po/interpreter_unittest.py
+++ b/util/ec3po/interpreter_unittest.py
@@ -10,371 +10,389 @@
from __future__ import print_function
import logging
-import mock
import tempfile
import unittest
+import mock
import six
-
-from ec3po import interpreter
-from ec3po import threadproc_shim
+from ec3po import interpreter, threadproc_shim
def GetBuiltins(func):
- if six.PY2:
- return '__builtin__.' + func
- return 'builtins.' + func
+ if six.PY2:
+ return "__builtin__." + func
+ return "builtins." + func
class TestEnhancedECBehaviour(unittest.TestCase):
- """Test case to verify all enhanced EC interpretation tasks."""
- def setUp(self):
- """Setup the test harness."""
- # Setup logging with a timestamp, the module, and the log level.
- logging.basicConfig(level=logging.DEBUG,
- format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
-
- # Create a tempfile that would represent the EC UART PTY.
- self.tempfile = tempfile.NamedTemporaryFile()
-
- # Create the pipes that the interpreter will use.
- self.cmd_pipe_user, self.cmd_pipe_itpr = threadproc_shim.Pipe()
- self.dbg_pipe_user, self.dbg_pipe_itpr = threadproc_shim.Pipe(duplex=False)
-
- # Mock the open() function so we can inspect reads/writes to the EC.
- self.ec_uart_pty = mock.mock_open()
-
- with mock.patch(GetBuiltins('open'), self.ec_uart_pty):
- # Create an interpreter.
- self.itpr = interpreter.Interpreter(self.tempfile.name,
- self.cmd_pipe_itpr,
- self.dbg_pipe_itpr,
- log_level=logging.DEBUG,
- name="EC")
-
- @mock.patch('ec3po.interpreter.os')
- def test_HandlingCommandsThatProduceNoOutput(self, mock_os):
- """Verify that the Interpreter correctly handles non-output commands.
-
- Args:
- mock_os: MagicMock object replacing the 'os' module for this test
- case.
- """
- # The interpreter init should open the EC UART PTY.
- expected_ec_calls = [mock.call(self.tempfile.name, 'r+b', buffering=0)]
- # Have a command come in the command pipe. The first command will be an
- # interrogation to determine if the EC is enhanced or not.
- self.cmd_pipe_user.send(interpreter.EC_SYN)
- self.itpr.HandleUserData()
- # At this point, the command should be queued up waiting to be sent, so
- # let's actually send it to the EC.
- self.itpr.SendCmdToEC()
- expected_ec_calls.extend([mock.call().write(interpreter.EC_SYN),
- mock.call().flush()])
- # Now, assume that the EC sends only 1 response back of EC_ACK.
- mock_os.read.side_effect = [interpreter.EC_ACK]
- # When reading the EC, the interpreter will call file.fileno() to pass to
- # os.read().
- expected_ec_calls.append(mock.call().fileno())
- # Simulate the response.
- self.itpr.HandleECData()
-
- # Now that the interrogation was complete, it's time to send down the real
- # command.
- test_cmd = b'chan save'
- # Send the test command down the pipe.
- self.cmd_pipe_user.send(test_cmd)
- self.itpr.HandleUserData()
- self.itpr.SendCmdToEC()
- # Since the EC image is enhanced, we should have sent a packed command.
- expected_ec_calls.append(mock.call().write(self.itpr.PackCommand(test_cmd)))
- expected_ec_calls.append(mock.call().flush())
-
- # Now that the first command was sent, we should send another command which
- # produces no output. The console would send another interrogation.
- self.cmd_pipe_user.send(interpreter.EC_SYN)
- self.itpr.HandleUserData()
- self.itpr.SendCmdToEC()
- expected_ec_calls.extend([mock.call().write(interpreter.EC_SYN),
- mock.call().flush()])
- # Again, assume that the EC sends only 1 response back of EC_ACK.
- mock_os.read.side_effect = [interpreter.EC_ACK]
- # When reading the EC, the interpreter will call file.fileno() to pass to
- # os.read().
- expected_ec_calls.append(mock.call().fileno())
- # Simulate the response.
- self.itpr.HandleECData()
-
- # Now send the second test command.
- test_cmd = b'chan 0'
- self.cmd_pipe_user.send(test_cmd)
- self.itpr.HandleUserData()
- self.itpr.SendCmdToEC()
- # Since the EC image is enhanced, we should have sent a packed command.
- expected_ec_calls.append(mock.call().write(self.itpr.PackCommand(test_cmd)))
- expected_ec_calls.append(mock.call().flush())
-
- # Finally, verify that the appropriate writes were actually sent to the EC.
- self.ec_uart_pty.assert_has_calls(expected_ec_calls)
-
- @mock.patch('ec3po.interpreter.os')
- def test_CommandRetryingOnError(self, mock_os):
- """Verify that commands are retried if an error is encountered.
-
- Args:
- mock_os: MagicMock object replacing the 'os' module for this test
- case.
- """
- # The interpreter init should open the EC UART PTY.
- expected_ec_calls = [mock.call(self.tempfile.name, 'r+b', buffering=0)]
- # Have a command come in the command pipe. The first command will be an
- # interrogation to determine if the EC is enhanced or not.
- self.cmd_pipe_user.send(interpreter.EC_SYN)
- self.itpr.HandleUserData()
- # At this point, the command should be queued up waiting to be sent, so
- # let's actually send it to the EC.
- self.itpr.SendCmdToEC()
- expected_ec_calls.extend([mock.call().write(interpreter.EC_SYN),
- mock.call().flush()])
- # Now, assume that the EC sends only 1 response back of EC_ACK.
- mock_os.read.side_effect = [interpreter.EC_ACK]
- # When reading the EC, the interpreter will call file.fileno() to pass to
- # os.read().
- expected_ec_calls.append(mock.call().fileno())
- # Simulate the response.
- self.itpr.HandleECData()
-
- # Let's send a command that is received on the EC-side with an error.
- test_cmd = b'accelinfo'
- self.cmd_pipe_user.send(test_cmd)
- self.itpr.HandleUserData()
- self.itpr.SendCmdToEC()
- packed_cmd = self.itpr.PackCommand(test_cmd)
- expected_ec_calls.extend([mock.call().write(packed_cmd),
- mock.call().flush()])
- # Have the EC return the error string twice.
- mock_os.read.side_effect = [b'&&EE', b'&&EE']
- for i in range(2):
- # When reading the EC, the interpreter will call file.fileno() to pass to
- # os.read().
- expected_ec_calls.append(mock.call().fileno())
- # Simulate the response.
- self.itpr.HandleECData()
-
- # Since an error was received, the EC should attempt to retry the command.
- expected_ec_calls.extend([mock.call().write(packed_cmd),
- mock.call().flush()])
- # Verify that the retry count was decremented.
- self.assertEqual(interpreter.COMMAND_RETRIES-i-1, self.itpr.cmd_retries,
- 'Unexpected cmd_remaining count.')
- # Actually retry the command.
- self.itpr.SendCmdToEC()
-
- # Now assume that the last one goes through with no trouble.
- expected_ec_calls.extend([mock.call().write(packed_cmd),
- mock.call().flush()])
- self.itpr.SendCmdToEC()
-
- # Verify all the calls.
- self.ec_uart_pty.assert_has_calls(expected_ec_calls)
-
- def test_PackCommandsForEnhancedEC(self):
- """Verify that the interpreter packs commands for enhanced EC images."""
- # Assume current EC image is enhanced.
- self.itpr.enhanced_ec = True
- # Receive a command from the user.
- test_cmd = b'gettime'
- self.cmd_pipe_user.send(test_cmd)
- # Mock out PackCommand to see if it was called.
- self.itpr.PackCommand = mock.MagicMock()
- # Have the interpreter handle the command.
- self.itpr.HandleUserData()
- # Verify that PackCommand() was called.
- self.itpr.PackCommand.assert_called_once_with(test_cmd)
-
- def test_DontPackCommandsForNonEnhancedEC(self):
- """Verify the interpreter doesn't pack commands for non-enhanced images."""
- # Assume current EC image is not enhanced.
- self.itpr.enhanced_ec = False
- # Receive a command from the user.
- test_cmd = b'gettime'
- self.cmd_pipe_user.send(test_cmd)
- # Mock out PackCommand to see if it was called.
- self.itpr.PackCommand = mock.MagicMock()
- # Have the interpreter handle the command.
- self.itpr.HandleUserData()
- # Verify that PackCommand() was called.
- self.itpr.PackCommand.assert_not_called()
-
- @mock.patch('ec3po.interpreter.os')
- def test_KeepingTrackOfInterrogation(self, mock_os):
- """Verify that the interpreter can track the state of the interrogation.
-
- Args:
- mock_os: MagicMock object replacing the 'os' module. for this test
- case.
- """
- # Upon init, the interpreter should assume that the current EC image is not
- # enhanced.
- self.assertFalse(self.itpr.enhanced_ec, msg=('State of enhanced_ec upon'
- ' init is not False.'))
-
- # Assume an interrogation request comes in from the user.
- self.cmd_pipe_user.send(interpreter.EC_SYN)
- self.itpr.HandleUserData()
-
- # Verify the state is now within an interrogation.
- self.assertTrue(self.itpr.interrogating, 'interrogating should be True')
- # The state of enhanced_ec should not be changed yet because we haven't
- # received a valid response yet.
- self.assertFalse(self.itpr.enhanced_ec, msg=('State of enhanced_ec is '
- 'not False.'))
-
- # Assume that the EC responds with an EC_ACK.
- mock_os.read.side_effect = [interpreter.EC_ACK]
- self.itpr.HandleECData()
-
- # Now, the interrogation should be complete and we should know that the
- # current EC image is enhanced.
- self.assertFalse(self.itpr.interrogating, msg=('interrogating should be '
- 'False'))
- self.assertTrue(self.itpr.enhanced_ec, msg='enhanced_ec sholud be True')
-
- # Now let's perform another interrogation, but pretend that the EC ignores
- # it.
- self.cmd_pipe_user.send(interpreter.EC_SYN)
- self.itpr.HandleUserData()
-
- # Verify interrogating state.
- self.assertTrue(self.itpr.interrogating, 'interrogating sholud be True')
- # We should assume that the image is not enhanced until we get the valid
- # response.
- self.assertFalse(self.itpr.enhanced_ec, 'enhanced_ec should be False now.')
-
- # Let's pretend that we get a random debug print. This should clear the
- # interrogating flag.
- mock_os.read.side_effect = [b'[1660.593076 HC 0x103]']
- self.itpr.HandleECData()
-
- # Verify that interrogating flag is cleared and enhanced_ec is still False.
- self.assertFalse(self.itpr.interrogating, 'interrogating should be False.')
- self.assertFalse(self.itpr.enhanced_ec,
- 'enhanced_ec should still be False.')
+ """Test case to verify all enhanced EC interpretation tasks."""
+
+ def setUp(self):
+ """Setup the test harness."""
+ # Setup logging with a timestamp, the module, and the log level.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+
+ # Create a tempfile that would represent the EC UART PTY.
+ self.tempfile = tempfile.NamedTemporaryFile()
+
+ # Create the pipes that the interpreter will use.
+ self.cmd_pipe_user, self.cmd_pipe_itpr = threadproc_shim.Pipe()
+ self.dbg_pipe_user, self.dbg_pipe_itpr = threadproc_shim.Pipe(duplex=False)
+
+ # Mock the open() function so we can inspect reads/writes to the EC.
+ self.ec_uart_pty = mock.mock_open()
+
+ with mock.patch(GetBuiltins("open"), self.ec_uart_pty):
+ # Create an interpreter.
+ self.itpr = interpreter.Interpreter(
+ self.tempfile.name,
+ self.cmd_pipe_itpr,
+ self.dbg_pipe_itpr,
+ log_level=logging.DEBUG,
+ name="EC",
+ )
+
+ @mock.patch("ec3po.interpreter.os")
+ def test_HandlingCommandsThatProduceNoOutput(self, mock_os):
+ """Verify that the Interpreter correctly handles non-output commands.
+
+ Args:
+ mock_os: MagicMock object replacing the 'os' module for this test
+ case.
+ """
+ # The interpreter init should open the EC UART PTY.
+ expected_ec_calls = [mock.call(self.tempfile.name, "r+b", buffering=0)]
+ # Have a command come in the command pipe. The first command will be an
+ # interrogation to determine if the EC is enhanced or not.
+ self.cmd_pipe_user.send(interpreter.EC_SYN)
+ self.itpr.HandleUserData()
+ # At this point, the command should be queued up waiting to be sent, so
+ # let's actually send it to the EC.
+ self.itpr.SendCmdToEC()
+ expected_ec_calls.extend(
+ [mock.call().write(interpreter.EC_SYN), mock.call().flush()]
+ )
+ # Now, assume that the EC sends only 1 response back of EC_ACK.
+ mock_os.read.side_effect = [interpreter.EC_ACK]
+ # When reading the EC, the interpreter will call file.fileno() to pass to
+ # os.read().
+ expected_ec_calls.append(mock.call().fileno())
+ # Simulate the response.
+ self.itpr.HandleECData()
+
+ # Now that the interrogation was complete, it's time to send down the real
+ # command.
+ test_cmd = b"chan save"
+ # Send the test command down the pipe.
+ self.cmd_pipe_user.send(test_cmd)
+ self.itpr.HandleUserData()
+ self.itpr.SendCmdToEC()
+ # Since the EC image is enhanced, we should have sent a packed command.
+ expected_ec_calls.append(mock.call().write(self.itpr.PackCommand(test_cmd)))
+ expected_ec_calls.append(mock.call().flush())
+
+ # Now that the first command was sent, we should send another command which
+ # produces no output. The console would send another interrogation.
+ self.cmd_pipe_user.send(interpreter.EC_SYN)
+ self.itpr.HandleUserData()
+ self.itpr.SendCmdToEC()
+ expected_ec_calls.extend(
+ [mock.call().write(interpreter.EC_SYN), mock.call().flush()]
+ )
+ # Again, assume that the EC sends only 1 response back of EC_ACK.
+ mock_os.read.side_effect = [interpreter.EC_ACK]
+ # When reading the EC, the interpreter will call file.fileno() to pass to
+ # os.read().
+ expected_ec_calls.append(mock.call().fileno())
+ # Simulate the response.
+ self.itpr.HandleECData()
+
+ # Now send the second test command.
+ test_cmd = b"chan 0"
+ self.cmd_pipe_user.send(test_cmd)
+ self.itpr.HandleUserData()
+ self.itpr.SendCmdToEC()
+ # Since the EC image is enhanced, we should have sent a packed command.
+ expected_ec_calls.append(mock.call().write(self.itpr.PackCommand(test_cmd)))
+ expected_ec_calls.append(mock.call().flush())
+
+ # Finally, verify that the appropriate writes were actually sent to the EC.
+ self.ec_uart_pty.assert_has_calls(expected_ec_calls)
+
+ @mock.patch("ec3po.interpreter.os")
+ def test_CommandRetryingOnError(self, mock_os):
+ """Verify that commands are retried if an error is encountered.
+
+ Args:
+ mock_os: MagicMock object replacing the 'os' module for this test
+ case.
+ """
+ # The interpreter init should open the EC UART PTY.
+ expected_ec_calls = [mock.call(self.tempfile.name, "r+b", buffering=0)]
+ # Have a command come in the command pipe. The first command will be an
+ # interrogation to determine if the EC is enhanced or not.
+ self.cmd_pipe_user.send(interpreter.EC_SYN)
+ self.itpr.HandleUserData()
+ # At this point, the command should be queued up waiting to be sent, so
+ # let's actually send it to the EC.
+ self.itpr.SendCmdToEC()
+ expected_ec_calls.extend(
+ [mock.call().write(interpreter.EC_SYN), mock.call().flush()]
+ )
+ # Now, assume that the EC sends only 1 response back of EC_ACK.
+ mock_os.read.side_effect = [interpreter.EC_ACK]
+ # When reading the EC, the interpreter will call file.fileno() to pass to
+ # os.read().
+ expected_ec_calls.append(mock.call().fileno())
+ # Simulate the response.
+ self.itpr.HandleECData()
+
+ # Let's send a command that is received on the EC-side with an error.
+ test_cmd = b"accelinfo"
+ self.cmd_pipe_user.send(test_cmd)
+ self.itpr.HandleUserData()
+ self.itpr.SendCmdToEC()
+ packed_cmd = self.itpr.PackCommand(test_cmd)
+ expected_ec_calls.extend([mock.call().write(packed_cmd), mock.call().flush()])
+ # Have the EC return the error string twice.
+ mock_os.read.side_effect = [b"&&EE", b"&&EE"]
+ for i in range(2):
+ # When reading the EC, the interpreter will call file.fileno() to pass to
+ # os.read().
+ expected_ec_calls.append(mock.call().fileno())
+ # Simulate the response.
+ self.itpr.HandleECData()
+
+ # Since an error was received, the EC should attempt to retry the command.
+ expected_ec_calls.extend(
+ [mock.call().write(packed_cmd), mock.call().flush()]
+ )
+ # Verify that the retry count was decremented.
+ self.assertEqual(
+ interpreter.COMMAND_RETRIES - i - 1,
+ self.itpr.cmd_retries,
+ "Unexpected cmd_remaining count.",
+ )
+ # Actually retry the command.
+ self.itpr.SendCmdToEC()
+
+ # Now assume that the last one goes through with no trouble.
+ expected_ec_calls.extend([mock.call().write(packed_cmd), mock.call().flush()])
+ self.itpr.SendCmdToEC()
+
+ # Verify all the calls.
+ self.ec_uart_pty.assert_has_calls(expected_ec_calls)
+
+ def test_PackCommandsForEnhancedEC(self):
+ """Verify that the interpreter packs commands for enhanced EC images."""
+ # Assume current EC image is enhanced.
+ self.itpr.enhanced_ec = True
+ # Receive a command from the user.
+ test_cmd = b"gettime"
+ self.cmd_pipe_user.send(test_cmd)
+ # Mock out PackCommand to see if it was called.
+ self.itpr.PackCommand = mock.MagicMock()
+ # Have the interpreter handle the command.
+ self.itpr.HandleUserData()
+ # Verify that PackCommand() was called.
+ self.itpr.PackCommand.assert_called_once_with(test_cmd)
+
+ def test_DontPackCommandsForNonEnhancedEC(self):
+ """Verify the interpreter doesn't pack commands for non-enhanced images."""
+ # Assume current EC image is not enhanced.
+ self.itpr.enhanced_ec = False
+ # Receive a command from the user.
+ test_cmd = b"gettime"
+ self.cmd_pipe_user.send(test_cmd)
+ # Mock out PackCommand to see if it was called.
+ self.itpr.PackCommand = mock.MagicMock()
+ # Have the interpreter handle the command.
+ self.itpr.HandleUserData()
+ # Verify that PackCommand() was called.
+ self.itpr.PackCommand.assert_not_called()
+
+ @mock.patch("ec3po.interpreter.os")
+ def test_KeepingTrackOfInterrogation(self, mock_os):
+ """Verify that the interpreter can track the state of the interrogation.
+
+ Args:
+ mock_os: MagicMock object replacing the 'os' module. for this test
+ case.
+ """
+ # Upon init, the interpreter should assume that the current EC image is not
+ # enhanced.
+ self.assertFalse(
+ self.itpr.enhanced_ec,
+ msg=("State of enhanced_ec upon" " init is not False."),
+ )
+
+ # Assume an interrogation request comes in from the user.
+ self.cmd_pipe_user.send(interpreter.EC_SYN)
+ self.itpr.HandleUserData()
+
+ # Verify the state is now within an interrogation.
+ self.assertTrue(self.itpr.interrogating, "interrogating should be True")
+ # The state of enhanced_ec should not be changed yet because we haven't
+ # received a valid response yet.
+ self.assertFalse(
+ self.itpr.enhanced_ec, msg=("State of enhanced_ec is " "not False.")
+ )
+
+ # Assume that the EC responds with an EC_ACK.
+ mock_os.read.side_effect = [interpreter.EC_ACK]
+ self.itpr.HandleECData()
+
+ # Now, the interrogation should be complete and we should know that the
+ # current EC image is enhanced.
+ self.assertFalse(
+ self.itpr.interrogating, msg=("interrogating should be " "False")
+ )
+ self.assertTrue(self.itpr.enhanced_ec, msg="enhanced_ec sholud be True")
+
+ # Now let's perform another interrogation, but pretend that the EC ignores
+ # it.
+ self.cmd_pipe_user.send(interpreter.EC_SYN)
+ self.itpr.HandleUserData()
+
+ # Verify interrogating state.
+ self.assertTrue(self.itpr.interrogating, "interrogating sholud be True")
+ # We should assume that the image is not enhanced until we get the valid
+ # response.
+ self.assertFalse(self.itpr.enhanced_ec, "enhanced_ec should be False now.")
+
+ # Let's pretend that we get a random debug print. This should clear the
+ # interrogating flag.
+ mock_os.read.side_effect = [b"[1660.593076 HC 0x103]"]
+ self.itpr.HandleECData()
+
+ # Verify that interrogating flag is cleared and enhanced_ec is still False.
+ self.assertFalse(self.itpr.interrogating, "interrogating should be False.")
+ self.assertFalse(self.itpr.enhanced_ec, "enhanced_ec should still be False.")
class TestUARTDisconnection(unittest.TestCase):
- """Test case to verify interpreter disconnection/reconnection."""
- def setUp(self):
- """Setup the test harness."""
- # Setup logging with a timestamp, the module, and the log level.
- logging.basicConfig(level=logging.DEBUG,
- format=('%(asctime)s - %(module)s -'
- ' %(levelname)s - %(message)s'))
-
- # Create a tempfile that would represent the EC UART PTY.
- self.tempfile = tempfile.NamedTemporaryFile()
-
- # Create the pipes that the interpreter will use.
- self.cmd_pipe_user, self.cmd_pipe_itpr = threadproc_shim.Pipe()
- self.dbg_pipe_user, self.dbg_pipe_itpr = threadproc_shim.Pipe(duplex=False)
-
- # Mock the open() function so we can inspect reads/writes to the EC.
- self.ec_uart_pty = mock.mock_open()
-
- with mock.patch(GetBuiltins('open'), self.ec_uart_pty):
- # Create an interpreter.
- self.itpr = interpreter.Interpreter(self.tempfile.name,
- self.cmd_pipe_itpr,
- self.dbg_pipe_itpr,
- log_level=logging.DEBUG,
- name="EC")
-
- # First, check that interpreter is initialized to connected.
- self.assertTrue(self.itpr.connected, ('The interpreter should be'
- ' initialized in a connected state'))
-
- def test_DisconnectStopsECTraffic(self):
- """Verify that when in disconnected state, no debug prints are sent."""
- # Let's send a disconnect command through the command pipe.
- self.cmd_pipe_user.send(b'disconnect')
- self.itpr.HandleUserData()
-
- # Verify interpreter is disconnected from EC.
- self.assertFalse(self.itpr.connected, ('The interpreter should be'
- 'disconnected.'))
- # Verify that the EC UART is no longer a member of the inputs. The
- # interpreter will never pull data from the EC if it's not a member of the
- # inputs list.
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
-
- def test_CommandsDroppedWhenDisconnected(self):
- """Verify that when in disconnected state, commands are dropped."""
- # Send a command, followed by 'disconnect'.
- self.cmd_pipe_user.send(b'taskinfo')
- self.itpr.HandleUserData()
- self.cmd_pipe_user.send(b'disconnect')
- self.itpr.HandleUserData()
-
- # Verify interpreter is disconnected from EC.
- self.assertFalse(self.itpr.connected, ('The interpreter should be'
- 'disconnected.'))
- # Verify that the EC UART is no longer a member of the inputs nor outputs.
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
-
- # Have the user send a few more commands in the disconnected state.
- command = 'help\n'
- for char in command:
- self.cmd_pipe_user.send(char.encode('utf-8'))
- self.itpr.HandleUserData()
-
- # The command queue should be empty.
- self.assertEqual(0, self.itpr.ec_cmd_queue.qsize())
-
- # Now send the reconnect command.
- self.cmd_pipe_user.send(b'reconnect')
-
- with mock.patch(GetBuiltins('open'), mock.mock_open()):
- self.itpr.HandleUserData()
-
- # Verify interpreter is connected.
- self.assertTrue(self.itpr.connected)
- # Verify that EC UART is a member of the inputs.
- self.assertTrue(self.itpr.ec_uart_pty in self.itpr.inputs)
- # Since no command was sent after reconnection, verify that the EC UART is
- # not a member of the outputs.
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
-
- def test_ReconnectAllowsECTraffic(self):
- """Verify that when connected, EC UART traffic is allowed."""
- # Let's send a disconnect command through the command pipe.
- self.cmd_pipe_user.send(b'disconnect')
- self.itpr.HandleUserData()
-
- # Verify interpreter is disconnected.
- self.assertFalse(self.itpr.connected, ('The interpreter should be'
- 'disconnected.'))
- # Verify that the EC UART is no longer a member of the inputs nor outputs.
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
-
- # Issue reconnect command through the command pipe.
- self.cmd_pipe_user.send(b'reconnect')
-
- with mock.patch(GetBuiltins('open'), mock.mock_open()):
- self.itpr.HandleUserData()
-
- # Verify interpreter is connected.
- self.assertTrue(self.itpr.connected, ('The interpreter should be'
- 'connected.'))
- # Verify that the EC UART is now a member of the inputs.
- self.assertTrue(self.itpr.ec_uart_pty in self.itpr.inputs)
- # Since we have issued no commands during the disconnected state, no
- # commands are pending and therefore the PTY should not be added to the
- # outputs.
- self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
-
-
-if __name__ == '__main__':
- unittest.main()
+ """Test case to verify interpreter disconnection/reconnection."""
+
+ def setUp(self):
+ """Setup the test harness."""
+ # Setup logging with a timestamp, the module, and the log level.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=("%(asctime)s - %(module)s -" " %(levelname)s - %(message)s"),
+ )
+
+ # Create a tempfile that would represent the EC UART PTY.
+ self.tempfile = tempfile.NamedTemporaryFile()
+
+ # Create the pipes that the interpreter will use.
+ self.cmd_pipe_user, self.cmd_pipe_itpr = threadproc_shim.Pipe()
+ self.dbg_pipe_user, self.dbg_pipe_itpr = threadproc_shim.Pipe(duplex=False)
+
+ # Mock the open() function so we can inspect reads/writes to the EC.
+ self.ec_uart_pty = mock.mock_open()
+
+ with mock.patch(GetBuiltins("open"), self.ec_uart_pty):
+ # Create an interpreter.
+ self.itpr = interpreter.Interpreter(
+ self.tempfile.name,
+ self.cmd_pipe_itpr,
+ self.dbg_pipe_itpr,
+ log_level=logging.DEBUG,
+ name="EC",
+ )
+
+ # First, check that interpreter is initialized to connected.
+ self.assertTrue(
+ self.itpr.connected,
+ ("The interpreter should be" " initialized in a connected state"),
+ )
+
+ def test_DisconnectStopsECTraffic(self):
+ """Verify that when in disconnected state, no debug prints are sent."""
+ # Let's send a disconnect command through the command pipe.
+ self.cmd_pipe_user.send(b"disconnect")
+ self.itpr.HandleUserData()
+
+ # Verify interpreter is disconnected from EC.
+ self.assertFalse(
+ self.itpr.connected, ("The interpreter should be" "disconnected.")
+ )
+ # Verify that the EC UART is no longer a member of the inputs. The
+ # interpreter will never pull data from the EC if it's not a member of the
+ # inputs list.
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
+
+ def test_CommandsDroppedWhenDisconnected(self):
+ """Verify that when in disconnected state, commands are dropped."""
+ # Send a command, followed by 'disconnect'.
+ self.cmd_pipe_user.send(b"taskinfo")
+ self.itpr.HandleUserData()
+ self.cmd_pipe_user.send(b"disconnect")
+ self.itpr.HandleUserData()
+
+ # Verify interpreter is disconnected from EC.
+ self.assertFalse(
+ self.itpr.connected, ("The interpreter should be" "disconnected.")
+ )
+ # Verify that the EC UART is no longer a member of the inputs nor outputs.
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
+
+ # Have the user send a few more commands in the disconnected state.
+ command = "help\n"
+ for char in command:
+ self.cmd_pipe_user.send(char.encode("utf-8"))
+ self.itpr.HandleUserData()
+
+ # The command queue should be empty.
+ self.assertEqual(0, self.itpr.ec_cmd_queue.qsize())
+
+ # Now send the reconnect command.
+ self.cmd_pipe_user.send(b"reconnect")
+
+ with mock.patch(GetBuiltins("open"), mock.mock_open()):
+ self.itpr.HandleUserData()
+
+ # Verify interpreter is connected.
+ self.assertTrue(self.itpr.connected)
+ # Verify that EC UART is a member of the inputs.
+ self.assertTrue(self.itpr.ec_uart_pty in self.itpr.inputs)
+ # Since no command was sent after reconnection, verify that the EC UART is
+ # not a member of the outputs.
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
+
+ def test_ReconnectAllowsECTraffic(self):
+ """Verify that when connected, EC UART traffic is allowed."""
+ # Let's send a disconnect command through the command pipe.
+ self.cmd_pipe_user.send(b"disconnect")
+ self.itpr.HandleUserData()
+
+ # Verify interpreter is disconnected.
+ self.assertFalse(
+ self.itpr.connected, ("The interpreter should be" "disconnected.")
+ )
+ # Verify that the EC UART is no longer a member of the inputs nor outputs.
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.inputs)
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
+
+ # Issue reconnect command through the command pipe.
+ self.cmd_pipe_user.send(b"reconnect")
+
+ with mock.patch(GetBuiltins("open"), mock.mock_open()):
+ self.itpr.HandleUserData()
+
+ # Verify interpreter is connected.
+ self.assertTrue(self.itpr.connected, ("The interpreter should be" "connected."))
+ # Verify that the EC UART is now a member of the inputs.
+ self.assertTrue(self.itpr.ec_uart_pty in self.itpr.inputs)
+ # Since we have issued no commands during the disconnected state, no
+ # commands are pending and therefore the PTY should not be added to the
+ # outputs.
+ self.assertFalse(self.itpr.ec_uart_pty in self.itpr.outputs)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/util/ec3po/threadproc_shim.py b/util/ec3po/threadproc_shim.py
index da5440b1f3..c0b3ce0bf4 100644
--- a/util/ec3po/threadproc_shim.py
+++ b/util/ec3po/threadproc_shim.py
@@ -34,33 +34,34 @@ wait until after completing the TODO above to stop using multiprocessing.Pipe!
# Imports to bring objects into this namespace for users of this module.
from multiprocessing import Pipe
-from six.moves.queue import Queue
from threading import Thread as ThreadOrProcess
+from six.moves.queue import Queue
+
# True if this module has ec3po using subprocesses, False if using threads.
USING_SUBPROCS = False
def _DoNothing():
- """Do-nothing function for use as a callback with DoIf()."""
+ """Do-nothing function for use as a callback with DoIf()."""
def DoIf(subprocs=_DoNothing, threads=_DoNothing):
- """Return a callback or not based on ec3po use of subprocesses or threads.
+ """Return a callback or not based on ec3po use of subprocesses or threads.
- Args:
- subprocs: callback that does not require any args - This will be returned
- (not called!) if and only if ec3po is using subprocesses. This is
- OPTIONAL, the default value is a do-nothing callback that returns None.
- threads: callback that does not require any args - This will be returned
- (not called!) if and only if ec3po is using threads. This is OPTIONAL,
- the default value is a do-nothing callback that returns None.
+ Args:
+ subprocs: callback that does not require any args - This will be returned
+ (not called!) if and only if ec3po is using subprocesses. This is
+ OPTIONAL, the default value is a do-nothing callback that returns None.
+ threads: callback that does not require any args - This will be returned
+ (not called!) if and only if ec3po is using threads. This is OPTIONAL,
+ the default value is a do-nothing callback that returns None.
- Returns:
- Either the subprocs or threads argument will be returned.
- """
- return subprocs if USING_SUBPROCS else threads
+ Returns:
+ Either the subprocs or threads argument will be returned.
+ """
+ return subprocs if USING_SUBPROCS else threads
def Value(ctype, *args):
- return ctype(*args)
+ return ctype(*args)
diff --git a/util/ec_openocd.py b/util/ec_openocd.py
index a84c00643c..11956ffa1c 100755
--- a/util/ec_openocd.py
+++ b/util/ec_openocd.py
@@ -16,6 +16,7 @@ import time
Flashes and debugs the EC through openocd
"""
+
@dataclasses.dataclass
class BoardInfo:
gdb_variant: str
@@ -24,9 +25,7 @@ class BoardInfo:
# Debuggers for each board, OpenOCD currently only supports GDB
-boards = {
- "skyrim": BoardInfo("arm-none-eabi-gdb", 6, 4)
-}
+boards = {"skyrim": BoardInfo("arm-none-eabi-gdb", 6, 4)}
def create_openocd_args(interface, board):
@@ -36,9 +35,12 @@ def create_openocd_args(interface, board):
board_info = boards[board]
args = [
"openocd",
- "-f", f"interface/{interface}.cfg",
- "-c", "add_script_search_dir openocd",
- "-f", f"board/{board}.cfg",
+ "-f",
+ f"interface/{interface}.cfg",
+ "-c",
+ "add_script_search_dir openocd",
+ "-f",
+ f"board/{board}.cfg",
]
return args
@@ -53,11 +55,13 @@ def create_gdb_args(board, port, executable):
board_info.gdb_variant,
executable,
# GDB can't autodetect these according to OpenOCD
- "-ex", f"set remote hardware-breakpoint-limit {board_info.num_breakpoints}",
- "-ex", f"set remote hardware-watchpoint-limit {board_info.num_watchpoints}",
-
+ "-ex",
+ f"set remote hardware-breakpoint-limit {board_info.num_breakpoints}",
+ "-ex",
+ f"set remote hardware-watchpoint-limit {board_info.num_watchpoints}",
# Connect to OpenOCD
- "-ex", f"target extended-remote localhost:{port}",
+ "-ex",
+ f"target extended-remote localhost:{port}",
]
return args
diff --git a/util/flash_jlink.py b/util/flash_jlink.py
index 26c3c2e709..50a0bfca20 100755
--- a/util/flash_jlink.py
+++ b/util/flash_jlink.py
@@ -25,7 +25,6 @@ import sys
import tempfile
import time
-
DEFAULT_SEGGER_REMOTE_PORT = 19020
# Commands are documented here: https://wiki.segger.com/J-Link_Commander
@@ -41,27 +40,34 @@ exit
class BoardConfig:
"""Board configuration."""
+
def __init__(self, interface, device, flash_address):
self.interface = interface
self.device = device
self.flash_address = flash_address
-SWD_INTERFACE = 'SWD'
-STM32_DEFAULT_FLASH_ADDRESS = '0x8000000'
-DRAGONCLAW_CONFIG = BoardConfig(interface=SWD_INTERFACE, device='STM32F412CG',
- flash_address=STM32_DEFAULT_FLASH_ADDRESS)
-ICETOWER_CONFIG = BoardConfig(interface=SWD_INTERFACE, device='STM32H743ZI',
- flash_address=STM32_DEFAULT_FLASH_ADDRESS)
+SWD_INTERFACE = "SWD"
+STM32_DEFAULT_FLASH_ADDRESS = "0x8000000"
+DRAGONCLAW_CONFIG = BoardConfig(
+ interface=SWD_INTERFACE,
+ device="STM32F412CG",
+ flash_address=STM32_DEFAULT_FLASH_ADDRESS,
+)
+ICETOWER_CONFIG = BoardConfig(
+ interface=SWD_INTERFACE,
+ device="STM32H743ZI",
+ flash_address=STM32_DEFAULT_FLASH_ADDRESS,
+)
BOARD_CONFIGS = {
- 'dragonclaw': DRAGONCLAW_CONFIG,
- 'bloonchipper': DRAGONCLAW_CONFIG,
- 'nucleo-f412zg': DRAGONCLAW_CONFIG,
- 'dartmonkey': ICETOWER_CONFIG,
- 'icetower': ICETOWER_CONFIG,
- 'nucleo-dartmonkey': ICETOWER_CONFIG,
- 'nucleo-h743zi': ICETOWER_CONFIG,
+ "dragonclaw": DRAGONCLAW_CONFIG,
+ "bloonchipper": DRAGONCLAW_CONFIG,
+ "nucleo-f412zg": DRAGONCLAW_CONFIG,
+ "dartmonkey": ICETOWER_CONFIG,
+ "icetower": ICETOWER_CONFIG,
+ "nucleo-dartmonkey": ICETOWER_CONFIG,
+ "nucleo-h743zi": ICETOWER_CONFIG,
}
@@ -93,9 +99,11 @@ def is_tcp_port_open(host: str, tcp_port: int) -> bool:
def create_jlink_command_file(firmware_file, config):
tmp = tempfile.NamedTemporaryFile()
- tmp.write(JLINK_COMMANDS.format(FIRMWARE=firmware_file,
- FLASH_ADDRESS=config.flash_address).encode(
- 'utf-8'))
+ tmp.write(
+ JLINK_COMMANDS.format(
+ FIRMWARE=firmware_file, FLASH_ADDRESS=config.flash_address
+ ).encode("utf-8")
+ )
tmp.flush()
return tmp
@@ -106,8 +114,8 @@ def flash(jlink_exe, remote, device, interface, cmd_file):
]
if remote:
- logging.debug(f'Connecting to J-Link over TCP/IP {remote}.')
- remote_components = remote.split(':')
+ logging.debug(f"Connecting to J-Link over TCP/IP {remote}.")
+ remote_components = remote.split(":")
if len(remote_components) not in [1, 2]:
logging.debug(f'Given remote "{remote}" is malformed.')
return 1
@@ -118,7 +126,7 @@ def flash(jlink_exe, remote, device, interface, cmd_file):
except socket.gaierror as e:
logging.error(f'Failed to resolve host "{host}": {e}.')
return 1
- logging.debug(f'Resolved {host} as {ip}.')
+ logging.debug(f"Resolved {host} as {ip}.")
port = DEFAULT_SEGGER_REMOTE_PORT
if len(remote_components) == 2:
@@ -126,29 +134,36 @@ def flash(jlink_exe, remote, device, interface, cmd_file):
port = int(remote_components[1])
except ValueError:
logging.error(
- f'Given remote port "{remote_components[1]}" is malformed.')
+ f'Given remote port "{remote_components[1]}" is malformed.'
+ )
return 1
- remote = f'{ip}:{port}'
+ remote = f"{ip}:{port}"
- logging.debug(f'Checking connection to {remote}.')
+ logging.debug(f"Checking connection to {remote}.")
if not is_tcp_port_open(ip, port):
- logging.error(
- f"JLink server doesn't seem to be listening on {remote}.")
- logging.error('Ensure that JLinkRemoteServerCLExe is running.')
+ logging.error(f"JLink server doesn't seem to be listening on {remote}.")
+ logging.error("Ensure that JLinkRemoteServerCLExe is running.")
return 1
- cmd.extend(['-ip', remote])
-
- cmd.extend([
- '-device', device,
- '-if', interface,
- '-speed', 'auto',
- '-autoconnect', '1',
- '-CommandFile', cmd_file,
- ])
- logging.debug('Running command: "%s"', ' '.join(cmd))
+ cmd.extend(["-ip", remote])
+
+ cmd.extend(
+ [
+ "-device",
+ device,
+ "-if",
+ interface,
+ "-speed",
+ "auto",
+ "-autoconnect",
+ "1",
+ "-CommandFile",
+ cmd_file,
+ ]
+ )
+ logging.debug('Running command: "%s"', " ".join(cmd))
completed_process = subprocess.run(cmd) # pylint: disable=subprocess-run-check
- logging.debug('JLink return code: %d', completed_process.returncode)
+ logging.debug("JLink return code: %d", completed_process.returncode)
return completed_process.returncode
@@ -156,38 +171,42 @@ def main(argv: list):
parser = argparse.ArgumentParser()
- default_jlink = './JLink_Linux_V684a_x86_64/JLinkExe'
+ default_jlink = "./JLink_Linux_V684a_x86_64/JLinkExe"
if shutil.which(default_jlink) is None:
- default_jlink = 'JLinkExe'
- parser.add_argument(
- '--jlink', '-j',
- help='JLinkExe path (default: ' + default_jlink + ')',
- default=default_jlink)
-
+ default_jlink = "JLinkExe"
parser.add_argument(
- '--remote', '-n',
- help='Use TCP/IP host[:port] to connect to a J-Link or '
- 'JLinkRemoteServerCLExe. If unspecified, connect over USB.')
+ "--jlink",
+ "-j",
+ help="JLinkExe path (default: " + default_jlink + ")",
+ default=default_jlink,
+ )
- default_board = 'bloonchipper'
parser.add_argument(
- '--board', '-b',
- help='Board (default: ' + default_board + ')',
- default=default_board)
+ "--remote",
+ "-n",
+ help="Use TCP/IP host[:port] to connect to a J-Link or "
+ "JLinkRemoteServerCLExe. If unspecified, connect over USB.",
+ )
- default_firmware = os.path.join('./build', default_board, 'ec.bin')
+ default_board = "bloonchipper"
parser.add_argument(
- '--image', '-i',
- help='Firmware binary (default: ' + default_firmware + ')',
- default=default_firmware)
+ "--board",
+ "-b",
+ help="Board (default: " + default_board + ")",
+ default=default_board,
+ )
- log_level_choices = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
+ default_firmware = os.path.join("./build", default_board, "ec.bin")
parser.add_argument(
- '--log_level', '-l',
- choices=log_level_choices,
- default='DEBUG'
+ "--image",
+ "-i",
+ help="Firmware binary (default: " + default_firmware + ")",
+ default=default_firmware,
)
+ log_level_choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
+ parser.add_argument("--log_level", "-l", choices=log_level_choices, default="DEBUG")
+
args = parser.parse_args(argv)
logging.basicConfig(level=args.log_level)
@@ -201,11 +220,12 @@ def main(argv: list):
args.jlink = args.jlink
cmd_file = create_jlink_command_file(args.image, config)
- ret_code = flash(args.jlink, args.remote, config.device, config.interface,
- cmd_file.name)
+ ret_code = flash(
+ args.jlink, args.remote, config.device, config.interface, cmd_file.name
+ )
cmd_file.close()
return ret_code
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
diff --git a/util/fptool.py b/util/fptool.py
index 5d73302bbc..b7f2150289 100755
--- a/util/fptool.py
+++ b/util/fptool.py
@@ -19,14 +19,14 @@ def cmd_flash(args: argparse.Namespace) -> int:
disabled.
"""
- if not shutil.which('flash_fp_mcu'):
- print('Error - The flash_fp_mcu utility does not exist.')
+ if not shutil.which("flash_fp_mcu"):
+ print("Error - The flash_fp_mcu utility does not exist.")
return 1
- cmd = ['flash_fp_mcu']
+ cmd = ["flash_fp_mcu"]
if args.image:
if not os.path.isfile(args.image):
- print(f'Error - image {args.image} is not a file.')
+ print(f"Error - image {args.image} is not a file.")
return 1
cmd.append(args.image)
@@ -38,18 +38,17 @@ def cmd_flash(args: argparse.Namespace) -> int:
def main(argv: list) -> int:
parser = argparse.ArgumentParser(description=__doc__)
- subparsers = parser.add_subparsers(dest='subcommand', title='subcommands')
+ subparsers = parser.add_subparsers(dest="subcommand", title="subcommands")
# This method of setting required is more compatible with older python.
subparsers.required = True
# Parser for "flash" subcommand.
- parser_decrypt = subparsers.add_parser('flash', help=cmd_flash.__doc__)
- parser_decrypt.add_argument(
- 'image', nargs='?', help='Path to the firmware image')
+ parser_decrypt = subparsers.add_parser("flash", help=cmd_flash.__doc__)
+ parser_decrypt.add_argument("image", nargs="?", help="Path to the firmware image")
parser_decrypt.set_defaults(func=cmd_flash)
opts = parser.parse_args(argv)
return opts.func(opts)
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
diff --git a/util/inject-keys.py b/util/inject-keys.py
index bd10b693ad..d05d4fbed7 100755
--- a/util/inject-keys.py
+++ b/util/inject-keys.py
@@ -8,50 +8,124 @@
# Note: This is a py2/3 compatible file.
from __future__ import print_function
+
import string
import subprocess
import sys
-
-KEYMATRIX = {'`': (3, 1), '1': (6, 1), '2': (6, 4), '3': (6, 2), '4': (6, 3),
- '5': (3, 3), '6': (3, 6), '7': (6, 6), '8': (6, 5), '9': (6, 9),
- '0': (6, 8), '-': (3, 8), '=': (0, 8), 'q': (7, 1), 'w': (7, 4),
- 'e': (7, 2), 'r': (7, 3), 't': (2, 3), 'y': (2, 6), 'u': (7, 6),
- 'i': (7, 5), 'o': (7, 9), 'p': (7, 8), '[': (2, 8), ']': (2, 5),
- '\\': (3, 11), 'a': (4, 1), 's': (4, 4), 'd': (4, 2), 'f': (4, 3),
- 'g': (1, 3), 'h': (1, 6), 'j': (4, 6), 'k': (4, 5), 'l': (4, 9),
- ';': (4, 8), '\'': (1, 8), 'z': (5, 1), 'x': (5, 4), 'c': (5, 2),
- 'v': (5, 3), 'b': (0, 3), 'n': (0, 6), 'm': (5, 6), ',': (5, 5),
- '.': (5, 9), '/': (5, 8), ' ': (5, 11), '<right>': (6, 12),
- '<alt_r>': (0, 10), '<down>': (6, 11), '<tab>': (2, 1),
- '<f10>': (0, 4), '<shift_r>': (7, 7), '<ctrl_r>': (4, 0),
- '<esc>': (1, 1), '<backspace>': (1, 11), '<f2>': (3, 2),
- '<alt_l>': (6, 10), '<ctrl_l>': (2, 0), '<f1>': (0, 2),
- '<search>': (0, 1), '<f3>': (2, 2), '<f4>': (1, 2), '<f5>': (3, 4),
- '<f6>': (2, 4), '<f7>': (1, 4), '<f8>': (2, 9), '<f9>': (1, 9),
- '<up>': (7, 11), '<shift_l>': (5, 7), '<enter>': (4, 11),
- '<left>': (7, 12)}
-
-
-UNSHIFT_TABLE = { '~': '`', '!': '1', '@': '2', '#': '3', '$': '4',
- '%': '5', '^': '6', '&': '7', '*': '8', '(': '9',
- ')': '0', '_': '-', '+': '=', '{': '[', '}': ']',
- '|': '\\',
- ':': ';', '"': "'", '<': ',', '>': '.', '?': '/'}
+KEYMATRIX = {
+ "`": (3, 1),
+ "1": (6, 1),
+ "2": (6, 4),
+ "3": (6, 2),
+ "4": (6, 3),
+ "5": (3, 3),
+ "6": (3, 6),
+ "7": (6, 6),
+ "8": (6, 5),
+ "9": (6, 9),
+ "0": (6, 8),
+ "-": (3, 8),
+ "=": (0, 8),
+ "q": (7, 1),
+ "w": (7, 4),
+ "e": (7, 2),
+ "r": (7, 3),
+ "t": (2, 3),
+ "y": (2, 6),
+ "u": (7, 6),
+ "i": (7, 5),
+ "o": (7, 9),
+ "p": (7, 8),
+ "[": (2, 8),
+ "]": (2, 5),
+ "\\": (3, 11),
+ "a": (4, 1),
+ "s": (4, 4),
+ "d": (4, 2),
+ "f": (4, 3),
+ "g": (1, 3),
+ "h": (1, 6),
+ "j": (4, 6),
+ "k": (4, 5),
+ "l": (4, 9),
+ ";": (4, 8),
+ "'": (1, 8),
+ "z": (5, 1),
+ "x": (5, 4),
+ "c": (5, 2),
+ "v": (5, 3),
+ "b": (0, 3),
+ "n": (0, 6),
+ "m": (5, 6),
+ ",": (5, 5),
+ ".": (5, 9),
+ "/": (5, 8),
+ " ": (5, 11),
+ "<right>": (6, 12),
+ "<alt_r>": (0, 10),
+ "<down>": (6, 11),
+ "<tab>": (2, 1),
+ "<f10>": (0, 4),
+ "<shift_r>": (7, 7),
+ "<ctrl_r>": (4, 0),
+ "<esc>": (1, 1),
+ "<backspace>": (1, 11),
+ "<f2>": (3, 2),
+ "<alt_l>": (6, 10),
+ "<ctrl_l>": (2, 0),
+ "<f1>": (0, 2),
+ "<search>": (0, 1),
+ "<f3>": (2, 2),
+ "<f4>": (1, 2),
+ "<f5>": (3, 4),
+ "<f6>": (2, 4),
+ "<f7>": (1, 4),
+ "<f8>": (2, 9),
+ "<f9>": (1, 9),
+ "<up>": (7, 11),
+ "<shift_l>": (5, 7),
+ "<enter>": (4, 11),
+ "<left>": (7, 12),
+}
+
+
+UNSHIFT_TABLE = {
+ "~": "`",
+ "!": "1",
+ "@": "2",
+ "#": "3",
+ "$": "4",
+ "%": "5",
+ "^": "6",
+ "&": "7",
+ "*": "8",
+ "(": "9",
+ ")": "0",
+ "_": "-",
+ "+": "=",
+ "{": "[",
+ "}": "]",
+ "|": "\\",
+ ":": ";",
+ '"': "'",
+ "<": ",",
+ ">": ".",
+ "?": "/",
+}
for c in string.ascii_lowercase:
UNSHIFT_TABLE[c.upper()] = c
def inject_event(key, press):
- if len(key) >= 2 and key[0] != '<':
- key = '<' + key + '>'
+ if len(key) >= 2 and key[0] != "<":
+ key = "<" + key + ">"
if key not in KEYMATRIX:
print("%s: invalid key: %s" % (this_script, key))
sys.exit(1)
(row, col) = KEYMATRIX[key]
- subprocess.call(["ectool", "kbpress", str(row), str(col),
- "1" if press else "0"])
+ subprocess.call(["ectool", "kbpress", str(row), str(col), "1" if press else "0"])
def inject_key(key):
@@ -73,8 +147,10 @@ def inject_string(string):
def usage():
- print("Usage: %s [-s <string>] [-k <key>]" % this_script,
- "[-p <pressed-key>] [-r <released-key>] ...")
+ print(
+ "Usage: %s [-s <string>] [-k <key>]" % this_script,
+ "[-p <pressed-key>] [-r <released-key>] ...",
+ )
print("Examples:")
print("%s -s MyPassw0rd -k enter" % this_script)
print("%s -p ctrl_l -p alt_l -k f3 -r alt_l -r ctrl_l" % this_script)
@@ -85,7 +161,7 @@ def help():
print("Valid keys are:")
i = 0
for key in KEYMATRIX:
- print("%12s" % key, end='')
+ print("%12s" % key, end="")
i += 1
if i % 4 == 0:
print()
@@ -114,12 +190,13 @@ usage_check(arg_len > 1, "not enough arguments")
usage_check(arg_len % 2 == 1, "mismatched arguments")
for i in range(1, arg_len, 2):
- usage_check(sys.argv[i] in ("-s", "-k", "-p", "-r"),
- "unknown flag: %s" % sys.argv[i])
+ usage_check(
+ sys.argv[i] in ("-s", "-k", "-p", "-r"), "unknown flag: %s" % sys.argv[i]
+ )
for i in range(1, arg_len, 2):
flag = sys.argv[i]
- arg = sys.argv[i+1]
+ arg = sys.argv[i + 1]
if flag == "-s":
inject_string(arg)
elif flag == "-k":
diff --git a/util/kconfig_check.py b/util/kconfig_check.py
index d1eba8e62b..04cdf9a990 100755
--- a/util/kconfig_check.py
+++ b/util/kconfig_check.py
@@ -32,12 +32,13 @@ import sys
USE_KCONFIGLIB = False
try:
import kconfiglib
+
USE_KCONFIGLIB = True
except ImportError:
pass
# Where we put the new config_allowed file
-NEW_ALLOWED_FNAME = pathlib.Path('/tmp/new_config_allowed.txt')
+NEW_ALLOWED_FNAME = pathlib.Path("/tmp/new_config_allowed.txt")
def parse_args(argv):
@@ -49,38 +50,72 @@ def parse_args(argv):
Returns:
argparse.Namespace object containing the results
"""
- epilog = '''Checks that new ad-hoc CONFIG options are not introduced without
-a corresponding Kconfig option for Zephyr'''
+ epilog = """Checks that new ad-hoc CONFIG options are not introduced without
+a corresponding Kconfig option for Zephyr"""
parser = argparse.ArgumentParser(epilog=epilog)
- parser.add_argument('-a', '--allowed', type=str,
- default='util/config_allowed.txt',
- help='File containing list of allowed ad-hoc CONFIGs')
- parser.add_argument('-c', '--configs', type=str, default='.config',
- help='File containing CONFIG options to check')
- parser.add_argument('-d', '--use-defines', action='store_true',
- help='Lines in the configs file use #define')
parser.add_argument(
- '-D', '--debug', action='store_true',
- help='Enabling debugging (provides a full traceback on error)')
+ "-a",
+ "--allowed",
+ type=str,
+ default="util/config_allowed.txt",
+ help="File containing list of allowed ad-hoc CONFIGs",
+ )
+ parser.add_argument(
+ "-c",
+ "--configs",
+ type=str,
+ default=".config",
+ help="File containing CONFIG options to check",
+ )
+ parser.add_argument(
+ "-d",
+ "--use-defines",
+ action="store_true",
+ help="Lines in the configs file use #define",
+ )
+ parser.add_argument(
+ "-D",
+ "--debug",
+ action="store_true",
+ help="Enabling debugging (provides a full traceback on error)",
+ )
+ parser.add_argument(
+ "-i",
+ "--ignore",
+ action="append",
+ help="Kconfig options to ignore (without CONFIG_ prefix)",
+ )
parser.add_argument(
- '-i', '--ignore', action='append',
- help='Kconfig options to ignore (without CONFIG_ prefix)')
- parser.add_argument('-I', '--search-path', type=str, action='append',
- help='Search paths to look for Kconfigs')
- parser.add_argument('-p', '--prefix', type=str, default='PLATFORM_EC_',
- help='Prefix to string from Kconfig options')
- parser.add_argument('-s', '--srctree', type=str, default='zephyr/',
- help='Path to source tree to look for Kconfigs')
+ "-I",
+ "--search-path",
+ type=str,
+ action="append",
+ help="Search paths to look for Kconfigs",
+ )
+ parser.add_argument(
+ "-p",
+ "--prefix",
+ type=str,
+ default="PLATFORM_EC_",
+ help="Prefix to string from Kconfig options",
+ )
+ parser.add_argument(
+ "-s",
+ "--srctree",
+ type=str,
+ default="zephyr/",
+ help="Path to source tree to look for Kconfigs",
+ )
# TODO(sjg@chromium.org): The chroot uses a very old Python. Once it moves
# to 3.7 or later we can use this instead:
# subparsers = parser.add_subparsers(dest='cmd', required=True)
- subparsers = parser.add_subparsers(dest='cmd')
+ subparsers = parser.add_subparsers(dest="cmd")
subparsers.required = True
- subparsers.add_parser('build', help='Build new list of ad-hoc CONFIGs')
- subparsers.add_parser('check', help='Check for new ad-hoc CONFIGs')
+ subparsers.add_parser("build", help="Build new list of ad-hoc CONFIGs")
+ subparsers.add_parser("check", help="Check for new ad-hoc CONFIGs")
return parser.parse_args(argv)
@@ -107,6 +142,7 @@ class KconfigCheck:
the user is exhorted to add a new Kconfig. This helps avoid adding new ad-hoc
CONFIG options, eventually returning the number to zero.
"""
+
@classmethod
def find_new_adhoc(cls, configs, kconfigs, allowed):
"""Get a list of new ad-hoc CONFIG options
@@ -172,11 +208,12 @@ class KconfigCheck:
List of CONFIG_xxx options found in the file, with the 'CONFIG_'
prefix removed
"""
- with open(configs_file, 'r') as inf:
- configs = re.findall('%sCONFIG_([A-Za-z0-9_]*)%s' %
- ((use_defines and '#define ' or ''),
- (use_defines and ' ' or '')),
- inf.read())
+ with open(configs_file, "r") as inf:
+ configs = re.findall(
+ "%sCONFIG_([A-Za-z0-9_]*)%s"
+ % ((use_defines and "#define " or ""), (use_defines and " " or "")),
+ inf.read(),
+ )
return configs
@classmethod
@@ -190,8 +227,8 @@ class KconfigCheck:
List of CONFIG_xxx options found in the file, with the 'CONFIG_'
prefix removed
"""
- with open(allowed_file, 'r') as inf:
- configs = re.findall('CONFIG_([A-Za-z0-9_]*)', inf.read())
+ with open(allowed_file, "r") as inf:
+ configs = re.findall("CONFIG_([A-Za-z0-9_]*)", inf.read())
return configs
@classmethod
@@ -209,15 +246,17 @@ class KconfigCheck:
"""
kconfig_files = []
for root, dirs, files in os.walk(srcdir):
- kconfig_files += [os.path.join(root, fname)
- for fname in files if fname.startswith('Kconfig')]
- if 'Kconfig' in dirs:
- dirs.remove('Kconfig')
+ kconfig_files += [
+ os.path.join(root, fname)
+ for fname in files
+ if fname.startswith("Kconfig")
+ ]
+ if "Kconfig" in dirs:
+ dirs.remove("Kconfig")
return kconfig_files
@classmethod
- def scan_kconfigs(cls, srcdir, prefix='', search_paths=None,
- try_kconfiglib=True):
+ def scan_kconfigs(cls, srcdir, prefix="", search_paths=None, try_kconfiglib=True):
"""Scan a source tree for Kconfig options
Args:
@@ -231,31 +270,40 @@ class KconfigCheck:
List of config and menuconfig options found
"""
if USE_KCONFIGLIB and try_kconfiglib:
- os.environ['srctree'] = srcdir
- kconf = kconfiglib.Kconfig('Kconfig', warn=False,
- search_paths=search_paths,
- allow_empty_macros=True)
+ os.environ["srctree"] = srcdir
+ kconf = kconfiglib.Kconfig(
+ "Kconfig",
+ warn=False,
+ search_paths=search_paths,
+ allow_empty_macros=True,
+ )
# There is always a MODULES config, since kconfiglib is designed for
# linux, but we don't want it
- kconfigs = [name for name in kconf.syms if name != 'MODULES']
+ kconfigs = [name for name in kconf.syms if name != "MODULES"]
if prefix:
- re_drop_prefix = re.compile(r'^%s' % prefix)
- kconfigs = [re_drop_prefix.sub('', name) for name in kconfigs]
+ re_drop_prefix = re.compile(r"^%s" % prefix)
+ kconfigs = [re_drop_prefix.sub("", name) for name in kconfigs]
else:
kconfigs = []
# Remove the prefix if present
- expr = re.compile(r'\n(config|menuconfig) (%s)?([A-Za-z0-9_]*)\n' %
- prefix)
+ expr = re.compile(r"\n(config|menuconfig) (%s)?([A-Za-z0-9_]*)\n" % prefix)
for fname in cls.find_kconfigs(srcdir):
with open(fname) as inf:
found = re.findall(expr, inf.read())
kconfigs += [name for kctype, _, name in found]
return sorted(kconfigs)
- def check_adhoc_configs(self, configs_file, srcdir, allowed_file,
- prefix='', use_defines=False, search_paths=None):
+ def check_adhoc_configs(
+ self,
+ configs_file,
+ srcdir,
+ allowed_file,
+ prefix="",
+ use_defines=False,
+ search_paths=None,
+ ):
"""Find new and unneeded ad-hoc configs in the configs_file
Args:
@@ -283,8 +331,9 @@ class KconfigCheck:
except kconfiglib.KconfigError:
# If we don't actually have access to the full Kconfig then we may
# get an error. Fall back to using manual methods.
- kconfigs = self.scan_kconfigs(srcdir, prefix, search_paths,
- try_kconfiglib=False)
+ kconfigs = self.scan_kconfigs(
+ srcdir, prefix, search_paths, try_kconfiglib=False
+ )
allowed = self.read_allowed(allowed_file)
new_adhoc = self.find_new_adhoc(configs, kconfigs, allowed)
@@ -292,8 +341,16 @@ class KconfigCheck:
updated_adhoc = self.get_updated_adhoc(unneeded_adhoc, allowed)
return new_adhoc, unneeded_adhoc, updated_adhoc
- def do_check(self, configs_file, srcdir, allowed_file, prefix, use_defines,
- search_paths, ignore=None):
+ def do_check(
+ self,
+ configs_file,
+ srcdir,
+ allowed_file,
+ prefix,
+ use_defines,
+ search_paths,
+ ignore=None,
+ ):
"""Find new ad-hoc configs in the configs_file
Args:
@@ -313,11 +370,12 @@ class KconfigCheck:
Exit code: 0 if OK, 1 if a problem was found
"""
new_adhoc, unneeded_adhoc, updated_adhoc = self.check_adhoc_configs(
- configs_file, srcdir, allowed_file, prefix, use_defines,
- search_paths)
+ configs_file, srcdir, allowed_file, prefix, use_defines, search_paths
+ )
if new_adhoc:
- file_list = '\n'.join(['CONFIG_%s' % name for name in new_adhoc])
- print(f'''Error:\tThe EC is in the process of migrating to Zephyr.
+ file_list = "\n".join(["CONFIG_%s" % name for name in new_adhoc])
+ print(
+ f"""Error:\tThe EC is in the process of migrating to Zephyr.
\tZephyr uses Kconfig for configuration rather than ad-hoc #defines.
\tAny new EC CONFIG options must ALSO be added to Zephyr so that new
\tfunctionality is available in Zephyr also. The following new ad-hoc
@@ -330,19 +388,21 @@ file in zephyr/ and add a 'config' or 'menuconfig' option.
Also see details in http://issuetracker.google.com/181253613
To temporarily disable this, use: ALLOW_CONFIG=1 make ...
-''', file=sys.stderr)
+""",
+ file=sys.stderr,
+ )
return 1
if not ignore:
ignore = []
unneeded_adhoc = [name for name in unneeded_adhoc if name not in ignore]
if unneeded_adhoc:
- with open(NEW_ALLOWED_FNAME, 'w') as out:
+ with open(NEW_ALLOWED_FNAME, "w") as out:
for config in updated_adhoc:
- print('CONFIG_%s' % config, file=out)
- now_in_kconfig = '\n'.join(
- ['CONFIG_%s' % name for name in unneeded_adhoc])
- print(f'''The following options are now in Kconfig:
+ print("CONFIG_%s" % config, file=out)
+ now_in_kconfig = "\n".join(["CONFIG_%s" % name for name in unneeded_adhoc])
+ print(
+ f"""The following options are now in Kconfig:
{now_in_kconfig}
@@ -350,12 +410,14 @@ Please run this to update the list of allowed ad-hoc CONFIGs and include this
update in your CL:
cp {NEW_ALLOWED_FNAME} util/config_allowed.txt
-''')
+"""
+ )
return 1
return 0
- def do_build(self, configs_file, srcdir, allowed_file, prefix, use_defines,
- search_paths):
+ def do_build(
+ self, configs_file, srcdir, allowed_file, prefix, use_defines, search_paths
+ ):
"""Find new ad-hoc configs in the configs_file
Args:
@@ -372,13 +434,14 @@ update in your CL:
Exit code: 0 if OK, 1 if a problem was found
"""
new_adhoc, _, updated_adhoc = self.check_adhoc_configs(
- configs_file, srcdir, allowed_file, prefix, use_defines,
- search_paths)
- with open(NEW_ALLOWED_FNAME, 'w') as out:
+ configs_file, srcdir, allowed_file, prefix, use_defines, search_paths
+ )
+ with open(NEW_ALLOWED_FNAME, "w") as out:
combined = sorted(new_adhoc + updated_adhoc)
for config in combined:
- print(f'CONFIG_{config}', file=out)
- print(f'New list is in {NEW_ALLOWED_FNAME}')
+ print(f"CONFIG_{config}", file=out)
+ print(f"New list is in {NEW_ALLOWED_FNAME}")
+
def main(argv):
"""Main function"""
@@ -386,18 +449,27 @@ def main(argv):
if not args.debug:
sys.tracebacklimit = 0
checker = KconfigCheck()
- if args.cmd == 'check':
+ if args.cmd == "check":
return checker.do_check(
- configs_file=args.configs, srcdir=args.srctree,
- allowed_file=args.allowed, prefix=args.prefix,
- use_defines=args.use_defines, search_paths=args.search_path,
- ignore=args.ignore)
- elif args.cmd == 'build':
- return checker.do_build(configs_file=args.configs, srcdir=args.srctree,
- allowed_file=args.allowed, prefix=args.prefix,
- use_defines=args.use_defines, search_paths=args.search_path)
+ configs_file=args.configs,
+ srcdir=args.srctree,
+ allowed_file=args.allowed,
+ prefix=args.prefix,
+ use_defines=args.use_defines,
+ search_paths=args.search_path,
+ ignore=args.ignore,
+ )
+ elif args.cmd == "build":
+ return checker.do_build(
+ configs_file=args.configs,
+ srcdir=args.srctree,
+ allowed_file=args.allowed,
+ prefix=args.prefix,
+ use_defines=args.use_defines,
+ search_paths=args.search_path,
+ )
return 2
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
diff --git a/util/kconfiglib.py b/util/kconfiglib.py
index 0e05aaaeac..a0033bba2d 100644
--- a/util/kconfiglib.py
+++ b/util/kconfiglib.py
@@ -553,7 +553,6 @@ import sys
from glob import iglob
from os.path import dirname, exists, expandvars, islink, join, realpath
-
VERSION = (14, 1, 0)
@@ -810,6 +809,7 @@ class Kconfig(object):
The current parsing location, for use in Python preprocessor functions.
See the module docstring.
"""
+
__slots__ = (
"_encoding",
"_functions",
@@ -848,7 +848,6 @@ class Kconfig(object):
"warn_to_stderr",
"warnings",
"y",
-
# Parsing-related
"_parsing_kconfigs",
"_readline",
@@ -866,9 +865,16 @@ class Kconfig(object):
# Public interface
#
- def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True,
- encoding="utf-8", suppress_traceback=False, search_paths=None,
- allow_empty_macros=False):
+ def __init__(
+ self,
+ filename="Kconfig",
+ warn=True,
+ warn_to_stderr=True,
+ encoding="utf-8",
+ suppress_traceback=False,
+ search_paths=None,
+ allow_empty_macros=False,
+ ):
"""
Creates a new Kconfig object by parsing Kconfig files.
Note that Kconfig files are not the same as .config files (which store
@@ -972,8 +978,14 @@ class Kconfig(object):
Pass True here to allow empty / undefined macros.
"""
try:
- self._init(filename, warn, warn_to_stderr, encoding, search_paths,
- allow_empty_macros)
+ self._init(
+ filename,
+ warn,
+ warn_to_stderr,
+ encoding,
+ search_paths,
+ allow_empty_macros,
+ )
except (EnvironmentError, KconfigError) as e:
if suppress_traceback:
cmd = sys.argv[0] # Empty string if missing
@@ -985,8 +997,9 @@ class Kconfig(object):
sys.exit(cmd + str(e).strip())
raise
- def _init(self, filename, warn, warn_to_stderr, encoding, search_paths,
- allow_empty_macros):
+ def _init(
+ self, filename, warn, warn_to_stderr, encoding, search_paths, allow_empty_macros
+ ):
# See __init__()
self._encoding = encoding
@@ -1011,8 +1024,9 @@ class Kconfig(object):
self.config_prefix = os.getenv("CONFIG_", "CONFIG_")
# Regular expressions for parsing .config files
self._set_match = _re_match(self.config_prefix + r"([^=]+)=(.*)")
- self._unset_match = _re_match(r"# {}([^ ]+) is not set".format(
- self.config_prefix))
+ self._unset_match = _re_match(
+ r"# {}([^ ]+) is not set".format(self.config_prefix)
+ )
self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "")
self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "")
@@ -1050,11 +1064,11 @@ class Kconfig(object):
# Predefined preprocessor functions, with min/max number of arguments
self._functions = {
- "info": (_info_fn, 1, 1),
- "error-if": (_error_if_fn, 2, 2),
- "filename": (_filename_fn, 0, 0),
- "lineno": (_lineno_fn, 0, 0),
- "shell": (_shell_fn, 1, 1),
+ "info": (_info_fn, 1, 1),
+ "error-if": (_error_if_fn, 2, 2),
+ "filename": (_filename_fn, 0, 0),
+ "lineno": (_lineno_fn, 0, 0),
+ "shell": (_shell_fn, 1, 1),
"warning-if": (_warning_if_fn, 2, 2),
}
@@ -1063,7 +1077,8 @@ class Kconfig(object):
self._functions.update(
importlib.import_module(
os.getenv("KCONFIG_FUNCTIONS", "kconfigfunctions")
- ).functions)
+ ).functions
+ )
except ImportError:
pass
@@ -1138,8 +1153,7 @@ class Kconfig(object):
# KCONFIG_STRICT is an older alias for KCONFIG_WARN_UNDEF, supported
# for backwards compatibility
- if os.getenv("KCONFIG_WARN_UNDEF") == "y" or \
- os.getenv("KCONFIG_STRICT") == "y":
+ if os.getenv("KCONFIG_WARN_UNDEF") == "y" or os.getenv("KCONFIG_STRICT") == "y":
self._check_undef_syms()
@@ -1247,15 +1261,14 @@ class Kconfig(object):
msg = None
if filename is None:
filename = standard_config_filename()
- if not exists(filename) and \
- not exists(join(self.srctree, filename)):
+ if not exists(filename) and not exists(join(self.srctree, filename)):
defconfig = self.defconfig_filename
if defconfig is None:
- return "Using default symbol values (no '{}')" \
- .format(filename)
+ return "Using default symbol values (no '{}')".format(filename)
- msg = " default configuration '{}' (no '{}')" \
- .format(defconfig, filename)
+ msg = " default configuration '{}' (no '{}')".format(
+ defconfig, filename
+ )
filename = defconfig
if not msg:
@@ -1313,15 +1326,20 @@ class Kconfig(object):
if sym.orig_type in _BOOL_TRISTATE:
# The C implementation only checks the first character
# to the right of '=', for whatever reason
- if not (sym.orig_type is BOOL
- and val.startswith(("y", "n")) or
- sym.orig_type is TRISTATE
- and val.startswith(("y", "m", "n"))):
- self._warn("'{}' is not a valid value for the {} "
- "symbol {}. Assignment ignored."
- .format(val, TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc),
- filename, linenr)
+ if not (
+ sym.orig_type is BOOL
+ and val.startswith(("y", "n"))
+ or sym.orig_type is TRISTATE
+ and val.startswith(("y", "m", "n"))
+ ):
+ self._warn(
+ "'{}' is not a valid value for the {} "
+ "symbol {}. Assignment ignored.".format(
+ val, TYPE_TO_STR[sym.orig_type], sym.name_and_loc
+ ),
+ filename,
+ linenr,
+ )
continue
val = val[0]
@@ -1332,12 +1350,14 @@ class Kconfig(object):
# to the choice symbols
prev_mode = sym.choice.user_value
- if prev_mode is not None and \
- TRI_TO_STR[prev_mode] != val:
+ if prev_mode is not None and TRI_TO_STR[prev_mode] != val:
- self._warn("both m and y assigned to symbols "
- "within the same choice",
- filename, linenr)
+ self._warn(
+ "both m and y assigned to symbols "
+ "within the same choice",
+ filename,
+ linenr,
+ )
# Set the choice's mode
sym.choice.set_value(val)
@@ -1345,10 +1365,14 @@ class Kconfig(object):
elif sym.orig_type is STRING:
match = _conf_string_match(val)
if not match:
- self._warn("malformed string literal in "
- "assignment to {}. Assignment ignored."
- .format(sym.name_and_loc),
- filename, linenr)
+ self._warn(
+ "malformed string literal in "
+ "assignment to {}. Assignment ignored.".format(
+ sym.name_and_loc
+ ),
+ filename,
+ linenr,
+ )
continue
val = unescape(match.group(1))
@@ -1361,9 +1385,11 @@ class Kconfig(object):
# lines or comments. 'line' has already been
# rstrip()'d, so blank lines show up as "" here.
if line and not line.lstrip().startswith("#"):
- self._warn("ignoring malformed line '{}'"
- .format(line),
- filename, linenr)
+ self._warn(
+ "ignoring malformed line '{}'".format(line),
+ filename,
+ linenr,
+ )
continue
@@ -1403,8 +1429,12 @@ class Kconfig(object):
self.missing_syms.append((name, val))
if self.warn_assign_undef:
self._warn(
- "attempt to assign the value '{}' to the undefined symbol {}"
- .format(val, name), filename, linenr)
+ "attempt to assign the value '{}' to the undefined symbol {}".format(
+ val, name
+ ),
+ filename,
+ linenr,
+ )
def _assigned_twice(self, sym, new_val, filename, linenr):
# Called when a symbol is assigned more than once in a .config file
@@ -1416,7 +1446,8 @@ class Kconfig(object):
user_val = sym.user_value
msg = '{} set more than once. Old value "{}", new value "{}".'.format(
- sym.name_and_loc, user_val, new_val)
+ sym.name_and_loc, user_val, new_val
+ )
if user_val == new_val:
if self.warn_assign_redun:
@@ -1482,8 +1513,7 @@ class Kconfig(object):
in tools, which can do e.g. print(kconf.write_autoconf()).
"""
if filename is None:
- filename = os.getenv("KCONFIG_AUTOHEADER",
- "include/generated/autoconf.h")
+ filename = os.getenv("KCONFIG_AUTOHEADER", "include/generated/autoconf.h")
if self._write_if_changed(filename, self._autoconf_contents(header)):
return "Kconfig header saved to '{}'".format(filename)
@@ -1512,28 +1542,26 @@ class Kconfig(object):
if sym.orig_type in _BOOL_TRISTATE:
if val == "y":
- add("#define {}{} 1\n"
- .format(self.config_prefix, sym.name))
+ add("#define {}{} 1\n".format(self.config_prefix, sym.name))
elif val == "m":
- add("#define {}{}_MODULE 1\n"
- .format(self.config_prefix, sym.name))
+ add("#define {}{}_MODULE 1\n".format(self.config_prefix, sym.name))
elif sym.orig_type is STRING:
- add('#define {}{} "{}"\n'
- .format(self.config_prefix, sym.name, escape(val)))
+ add(
+ '#define {}{} "{}"\n'.format(
+ self.config_prefix, sym.name, escape(val)
+ )
+ )
else: # sym.orig_type in _INT_HEX:
- if sym.orig_type is HEX and \
- not val.startswith(("0x", "0X")):
+ if sym.orig_type is HEX and not val.startswith(("0x", "0X")):
val = "0x" + val
- add("#define {}{} {}\n"
- .format(self.config_prefix, sym.name, val))
+ add("#define {}{} {}\n".format(self.config_prefix, sym.name, val))
return "".join(chunks)
- def write_config(self, filename=None, header=None, save_old=True,
- verbose=None):
+ def write_config(self, filename=None, header=None, save_old=True, verbose=None):
r"""
Writes out symbol values in the .config format. The format matches the
C implementation, including ordering.
@@ -1647,9 +1675,12 @@ class Kconfig(object):
node = node.parent
# Add a comment when leaving visible menus
- if node.item is MENU and expr_value(node.dep) and \
- expr_value(node.visibility) and \
- node is not self.top_node:
+ if (
+ node.item is MENU
+ and expr_value(node.dep)
+ and expr_value(node.visibility)
+ and node is not self.top_node
+ ):
add("# end of {}\n".format(node.prompt[0]))
after_end_comment = True
@@ -1680,9 +1711,9 @@ class Kconfig(object):
add("\n")
add(conf_string)
- elif expr_value(node.dep) and \
- ((item is MENU and expr_value(node.visibility)) or
- item is COMMENT):
+ elif expr_value(node.dep) and (
+ (item is MENU and expr_value(node.visibility)) or item is COMMENT
+ ):
add("\n#\n# {}\n#\n".format(node.prompt[0]))
after_end_comment = False
@@ -1738,8 +1769,7 @@ class Kconfig(object):
# Skip symbols that cannot be changed. Only check
# non-choice symbols, as selects don't affect choice
# symbols.
- if not sym.choice and \
- sym.visibility <= expr_value(sym.rev_dep):
+ if not sym.choice and sym.visibility <= expr_value(sym.rev_dep):
continue
# Skip symbols whose value matches their default
@@ -1750,11 +1780,13 @@ class Kconfig(object):
# choice, unless the choice is optional or the symbol type
# isn't bool (it might be possible to set the choice mode
# to n or the symbol to m in those cases).
- if sym.choice and \
- not sym.choice.is_optional and \
- sym.choice._selection_from_defaults() is sym and \
- sym.orig_type is BOOL and \
- sym.tri_value == 2:
+ if (
+ sym.choice
+ and not sym.choice.is_optional
+ and sym.choice._selection_from_defaults() is sym
+ and sym.orig_type is BOOL
+ and sym.tri_value == 2
+ ):
continue
add(sym.config_string)
@@ -1842,9 +1874,11 @@ class Kconfig(object):
# making a missing symbol logically equivalent to n
if sym._write_to_conf:
- if sym._old_val is None and \
- sym.orig_type in _BOOL_TRISTATE and \
- val == "n":
+ if (
+ sym._old_val is None
+ and sym.orig_type in _BOOL_TRISTATE
+ and val == "n"
+ ):
# No old value (the symbol was missing or n), new value n.
# No change.
continue
@@ -1924,17 +1958,20 @@ class Kconfig(object):
# by passing a flag to it, plus we only need to look at symbols here.
self._write_if_changed(
- os.path.join(path, "auto.conf"),
- self._old_vals_contents())
+ os.path.join(path, "auto.conf"), self._old_vals_contents()
+ )
def _old_vals_contents(self):
# _write_old_vals() helper. Returns the contents to write as a string.
# Temporary list instead of generator makes this a bit faster
- return "".join([
- sym.config_string for sym in self.unique_defined_syms
+ return "".join(
+ [
+ sym.config_string
+ for sym in self.unique_defined_syms
if not (sym.orig_type in _BOOL_TRISTATE and not sym.tri_value)
- ])
+ ]
+ )
def node_iter(self, unique_syms=False):
"""
@@ -2112,30 +2149,35 @@ class Kconfig(object):
Returns a string with information about the Kconfig object when it is
evaluated on e.g. the interactive Python prompt.
"""
+
def status(flag):
return "enabled" if flag else "disabled"
- return "<{}>".format(", ".join((
- "configuration with {} symbols".format(len(self.syms)),
- 'main menu prompt "{}"'.format(self.mainmenu_text),
- "srctree is current directory" if not self.srctree else
- 'srctree "{}"'.format(self.srctree),
- 'config symbol prefix "{}"'.format(self.config_prefix),
- "warnings " + status(self.warn),
- "printing of warnings to stderr " + status(self.warn_to_stderr),
- "undef. symbol assignment warnings " +
- status(self.warn_assign_undef),
- "overriding symbol assignment warnings " +
- status(self.warn_assign_override),
- "redundant symbol assignment warnings " +
- status(self.warn_assign_redun)
- )))
+ return "<{}>".format(
+ ", ".join(
+ (
+ "configuration with {} symbols".format(len(self.syms)),
+ 'main menu prompt "{}"'.format(self.mainmenu_text),
+ "srctree is current directory"
+ if not self.srctree
+ else 'srctree "{}"'.format(self.srctree),
+ 'config symbol prefix "{}"'.format(self.config_prefix),
+ "warnings " + status(self.warn),
+ "printing of warnings to stderr " + status(self.warn_to_stderr),
+ "undef. symbol assignment warnings "
+ + status(self.warn_assign_undef),
+ "overriding symbol assignment warnings "
+ + status(self.warn_assign_override),
+ "redundant symbol assignment warnings "
+ + status(self.warn_assign_redun),
+ )
+ )
+ )
#
# Private methods
#
-
#
# File reading
#
@@ -2160,11 +2202,17 @@ class Kconfig(object):
e = e2
raise _KconfigIOError(
- e, "Could not open '{}' ({}: {}). Check that the $srctree "
- "environment variable ({}) is set correctly."
- .format(filename, errno.errorcode[e.errno], e.strerror,
- "set to '{}'".format(self.srctree) if self.srctree
- else "unset or blank"))
+ e,
+ "Could not open '{}' ({}: {}). Check that the $srctree "
+ "environment variable ({}) is set correctly.".format(
+ filename,
+ errno.errorcode[e.errno],
+ e.strerror,
+ "set to '{}'".format(self.srctree)
+ if self.srctree
+ else "unset or blank",
+ ),
+ )
def _enter_file(self, filename):
# Jumps to the beginning of a sourced Kconfig file, saving the previous
@@ -2179,7 +2227,7 @@ class Kconfig(object):
if filename.startswith(self._srctree_prefix):
# Relative path (or a redundant absolute path to within $srctree,
# but it's probably fine to reduce those too)
- rel_filename = filename[len(self._srctree_prefix):]
+ rel_filename = filename[len(self._srctree_prefix) :]
else:
# Absolute path
rel_filename = filename
@@ -2212,20 +2260,32 @@ class Kconfig(object):
raise KconfigError(
"\n{}:{}: recursive 'source' of '{}' detected. Check that "
"environment variables are set correctly.\n"
- "Include path:\n{}"
- .format(self.filename, self.linenr, rel_filename,
- "\n".join("{}:{}".format(name, linenr)
- for name, linenr in self._include_path)))
+ "Include path:\n{}".format(
+ self.filename,
+ self.linenr,
+ rel_filename,
+ "\n".join(
+ "{}:{}".format(name, linenr)
+ for name, linenr in self._include_path
+ ),
+ )
+ )
try:
self._readline = self._open(filename, "r").readline
except EnvironmentError as e:
# We already know that the file exists
raise _KconfigIOError(
- e, "{}:{}: Could not open '{}' (in '{}') ({}: {})"
- .format(self.filename, self.linenr, filename,
- self._line.strip(),
- errno.errorcode[e.errno], e.strerror))
+ e,
+ "{}:{}: Could not open '{}' (in '{}') ({}: {})".format(
+ self.filename,
+ self.linenr,
+ filename,
+ self._line.strip(),
+ errno.errorcode[e.errno],
+ e.strerror,
+ ),
+ )
self.filename = rel_filename
self.linenr = 0
@@ -2438,8 +2498,11 @@ class Kconfig(object):
else:
i = match.end()
- token = self.const_syms[name] if name in STR_TO_TRI else \
- self._lookup_sym(name)
+ token = (
+ self.const_syms[name]
+ if name in STR_TO_TRI
+ else self._lookup_sym(name)
+ )
else:
# It's a case of missing quotes. For example, the
@@ -2455,9 +2518,13 @@ class Kconfig(object):
# Named choices ('choice FOO') also end up here.
if token is not _T_CHOICE:
- self._warn("style: quotes recommended around '{}' in '{}'"
- .format(name, self._line.strip()),
- self.filename, self.linenr)
+ self._warn(
+ "style: quotes recommended around '{}' in '{}'".format(
+ name, self._line.strip()
+ ),
+ self.filename,
+ self.linenr,
+ )
token = name
i = match.end()
@@ -2476,7 +2543,7 @@ class Kconfig(object):
end_i = s.find(c, i + 1) + 1
if not end_i:
self._parse_error("unterminated string")
- val = s[i + 1:end_i - 1]
+ val = s[i + 1 : end_i - 1]
i = end_i
else:
# Slow path
@@ -2489,18 +2556,22 @@ class Kconfig(object):
#
# The preprocessor functionality changed how
# environment variables are referenced, to $(FOO).
- val = expandvars(s[i + 1:end_i - 1]
- .replace("$UNAME_RELEASE",
- _UNAME_RELEASE))
+ val = expandvars(
+ s[i + 1 : end_i - 1].replace(
+ "$UNAME_RELEASE", _UNAME_RELEASE
+ )
+ )
i = end_i
# This is the only place where we don't survive with a
# single token of lookback: 'option env="FOO"' does not
# refer to a constant symbol named "FOO".
- token = \
- val if token in _STRING_LEX or tokens[0] is _T_OPTION \
+ token = (
+ val
+ if token in _STRING_LEX or tokens[0] is _T_OPTION
else self._lookup_const_sym(val)
+ )
elif s.startswith("&&", i):
token = _T_AND
@@ -2533,7 +2604,6 @@ class Kconfig(object):
elif c == "#":
break
-
# Very rare
elif s.startswith("<=", i):
@@ -2552,16 +2622,13 @@ class Kconfig(object):
token = _T_GREATER
i += 1
-
else:
self._parse_error("unknown tokens in line")
-
# Skip trailing whitespace
while i < len(s) and s[i].isspace():
i += 1
-
# Add the token
tokens.append(token)
@@ -2652,7 +2719,6 @@ class Kconfig(object):
# Assigned variable
name = s[:i]
-
# Extract assignment operator (=, :=, or +=) and value
rhs_match = _assignment_rhs_match(s, i)
if not rhs_match:
@@ -2660,7 +2726,6 @@ class Kconfig(object):
op, val = rhs_match.groups()
-
if name in self.variables:
# Already seen variable
var = self.variables[name]
@@ -2686,8 +2751,9 @@ class Kconfig(object):
else: # op == "+="
# += does immediate expansion if the variable was last set
# with :=
- var.value += " " + (val if var.is_recursive else
- self._expand_whole(val, ()))
+ var.value += " " + (
+ val if var.is_recursive else self._expand_whole(val, ())
+ )
def _expand_whole(self, s, args):
# Expands preprocessor macros in all of 's'. Used whenever we don't
@@ -2753,7 +2819,6 @@ class Kconfig(object):
if not match:
self._parse_error("unterminated string")
-
if match.group() == quote:
# Found the end of the string
return (s, match.end())
@@ -2762,7 +2827,7 @@ class Kconfig(object):
# Replace '\x' with 'x'. 'i' ends up pointing to the character
# after 'x', which allows macros to be canceled with '\$(foo)'.
i = match.end()
- s = s[:match.start()] + s[i:]
+ s = s[: match.start()] + s[i:]
elif match.group() == "$(":
# A macro call within the string
@@ -2792,7 +2857,6 @@ class Kconfig(object):
if not match:
self._parse_error("missing end parenthesis in macro expansion")
-
if match.group() == "(":
nesting += 1
i = match.end()
@@ -2805,7 +2869,7 @@ class Kconfig(object):
# Found the end of the macro
- new_args.append(s[arg_start:match.start()])
+ new_args.append(s[arg_start : match.start()])
# $(1) is replaced by the first argument to the function, etc.,
# provided at least that many arguments were passed
@@ -2819,7 +2883,7 @@ class Kconfig(object):
# and also go through the function value path
res += self._fn_val(new_args)
- return (res + s[match.end():], len(res))
+ return (res + s[match.end() :], len(res))
elif match.group() == ",":
i = match.end()
@@ -2827,7 +2891,7 @@ class Kconfig(object):
continue
# Found the end of a macro argument
- new_args.append(s[arg_start:match.start()])
+ new_args.append(s[arg_start : match.start()])
arg_start = i
else: # match.group() == "$("
@@ -2847,13 +2911,17 @@ class Kconfig(object):
if len(args) == 1:
# Plain variable
if var._n_expansions:
- self._parse_error("Preprocessor variable {} recursively "
- "references itself".format(var.name))
+ self._parse_error(
+ "Preprocessor variable {} recursively "
+ "references itself".format(var.name)
+ )
elif var._n_expansions > 100:
# Allow functions to call themselves, but guess that functions
# that are overly recursive are stuck
- self._parse_error("Preprocessor function {} seems stuck "
- "in infinite recursion".format(var.name))
+ self._parse_error(
+ "Preprocessor function {} seems stuck "
+ "in infinite recursion".format(var.name)
+ )
var._n_expansions += 1
res = self._expand_whole(self.variables[fn].value, args)
@@ -2865,8 +2933,9 @@ class Kconfig(object):
py_fn, min_arg, max_arg = self._functions[fn]
- if len(args) - 1 < min_arg or \
- (max_arg is not None and len(args) - 1 > max_arg):
+ if len(args) - 1 < min_arg or (
+ max_arg is not None and len(args) - 1 > max_arg
+ ):
if min_arg == max_arg:
expected_args = min_arg
@@ -2875,10 +2944,12 @@ class Kconfig(object):
else:
expected_args = "{}-{}".format(min_arg, max_arg)
- raise KconfigError("{}:{}: bad number of arguments in call "
- "to {}, expected {}, got {}"
- .format(self.filename, self.linenr, fn,
- expected_args, len(args) - 1))
+ raise KconfigError(
+ "{}:{}: bad number of arguments in call "
+ "to {}, expected {}, got {}".format(
+ self.filename, self.linenr, fn, expected_args, len(args) - 1
+ )
+ )
return py_fn(self, *args)
@@ -2962,7 +3033,7 @@ class Kconfig(object):
node = MenuNode()
node.kconfig = self
node.item = sym
- node.is_menuconfig = (t0 is _T_MENUCONFIG)
+ node.is_menuconfig = t0 is _T_MENUCONFIG
node.prompt = node.help = node.list = None
node.parent = parent
node.filename = self.filename
@@ -2974,8 +3045,11 @@ class Kconfig(object):
self._parse_props(node)
if node.is_menuconfig and not node.prompt:
- self._warn("the menuconfig symbol {} has no prompt"
- .format(sym.name_and_loc))
+ self._warn(
+ "the menuconfig symbol {} has no prompt".format(
+ sym.name_and_loc
+ )
+ )
# Equivalent to
#
@@ -3014,11 +3088,16 @@ class Kconfig(object):
"{}:{}: '{}' not found (in '{}'). Check that "
"environment variables are set correctly (e.g. "
"$srctree, which is {}). Also note that unset "
- "environment variables expand to the empty string."
- .format(self.filename, self.linenr, pattern,
- self._line.strip(),
- "set to '{}'".format(self.srctree)
- if self.srctree else "unset or blank"))
+ "environment variables expand to the empty string.".format(
+ self.filename,
+ self.linenr,
+ pattern,
+ self._line.strip(),
+ "set to '{}'".format(self.srctree)
+ if self.srctree
+ else "unset or blank",
+ )
+ )
for filename in filenames:
self._enter_file(filename)
@@ -3125,20 +3204,28 @@ class Kconfig(object):
# A valid endchoice/endif/endmenu is caught by the 'end_token'
# check above
self._parse_error(
- "no corresponding 'choice'" if t0 is _T_ENDCHOICE else
- "no corresponding 'if'" if t0 is _T_ENDIF else
- "no corresponding 'menu'" if t0 is _T_ENDMENU else
- "unrecognized construct")
+ "no corresponding 'choice'"
+ if t0 is _T_ENDCHOICE
+ else "no corresponding 'if'"
+ if t0 is _T_ENDIF
+ else "no corresponding 'menu'"
+ if t0 is _T_ENDMENU
+ else "unrecognized construct"
+ )
# End of file reached. Return the last node.
if end_token:
raise KconfigError(
- "error: expected '{}' at end of '{}'"
- .format("endchoice" if end_token is _T_ENDCHOICE else
- "endif" if end_token is _T_ENDIF else
- "endmenu",
- self.filename))
+ "error: expected '{}' at end of '{}'".format(
+ "endchoice"
+ if end_token is _T_ENDCHOICE
+ else "endif"
+ if end_token is _T_ENDIF
+ else "endmenu",
+ self.filename,
+ )
+ )
return prev
@@ -3187,8 +3274,7 @@ class Kconfig(object):
if not self._check_token(_T_ON):
self._parse_error("expected 'on' after 'depends'")
- node.dep = self._make_and(node.dep,
- self._expect_expr_and_eol())
+ node.dep = self._make_and(node.dep, self._expect_expr_and_eol())
elif t0 is _T_HELP:
self._parse_help(node)
@@ -3197,42 +3283,40 @@ class Kconfig(object):
if node.item.__class__ is not Symbol:
self._parse_error("only symbols can select")
- node.selects.append((self._expect_nonconst_sym(),
- self._parse_cond()))
+ node.selects.append((self._expect_nonconst_sym(), self._parse_cond()))
elif t0 is None:
# Blank line
continue
elif t0 is _T_DEFAULT:
- node.defaults.append((self._parse_expr(False),
- self._parse_cond()))
+ node.defaults.append((self._parse_expr(False), self._parse_cond()))
elif t0 in _DEF_TOKEN_TO_TYPE:
self._set_type(node.item, _DEF_TOKEN_TO_TYPE[t0])
- node.defaults.append((self._parse_expr(False),
- self._parse_cond()))
+ node.defaults.append((self._parse_expr(False), self._parse_cond()))
elif t0 is _T_PROMPT:
self._parse_prompt(node)
elif t0 is _T_RANGE:
- node.ranges.append((self._expect_sym(), self._expect_sym(),
- self._parse_cond()))
+ node.ranges.append(
+ (self._expect_sym(), self._expect_sym(), self._parse_cond())
+ )
elif t0 is _T_IMPLY:
if node.item.__class__ is not Symbol:
self._parse_error("only symbols can imply")
- node.implies.append((self._expect_nonconst_sym(),
- self._parse_cond()))
+ node.implies.append((self._expect_nonconst_sym(), self._parse_cond()))
elif t0 is _T_VISIBLE:
if not self._check_token(_T_IF):
self._parse_error("expected 'if' after 'visible'")
- node.visibility = self._make_and(node.visibility,
- self._expect_expr_and_eol())
+ node.visibility = self._make_and(
+ node.visibility, self._expect_expr_and_eol()
+ )
elif t0 is _T_OPTION:
if self._check_token(_T_ENV):
@@ -3244,33 +3328,42 @@ class Kconfig(object):
if env_var in os.environ:
node.defaults.append(
- (self._lookup_const_sym(os.environ[env_var]),
- self.y))
+ (self._lookup_const_sym(os.environ[env_var]), self.y)
+ )
else:
- self._warn("{1} has 'option env=\"{0}\"', "
- "but the environment variable {0} is not "
- "set".format(node.item.name, env_var),
- self.filename, self.linenr)
+ self._warn(
+ "{1} has 'option env=\"{0}\"', "
+ "but the environment variable {0} is not "
+ "set".format(node.item.name, env_var),
+ self.filename,
+ self.linenr,
+ )
if env_var != node.item.name:
- self._warn("Kconfiglib expands environment variables "
- "in strings directly, meaning you do not "
- "need 'option env=...' \"bounce\" symbols. "
- "For compatibility with the C tools, "
- "rename {} to {} (so that the symbol name "
- "matches the environment variable name)."
- .format(node.item.name, env_var),
- self.filename, self.linenr)
+ self._warn(
+ "Kconfiglib expands environment variables "
+ "in strings directly, meaning you do not "
+ "need 'option env=...' \"bounce\" symbols. "
+ "For compatibility with the C tools, "
+ "rename {} to {} (so that the symbol name "
+ "matches the environment variable name).".format(
+ node.item.name, env_var
+ ),
+ self.filename,
+ self.linenr,
+ )
elif self._check_token(_T_DEFCONFIG_LIST):
if not self.defconfig_list:
self.defconfig_list = node.item
else:
- self._warn("'option defconfig_list' set on multiple "
- "symbols ({0} and {1}). Only {0} will be "
- "used.".format(self.defconfig_list.name,
- node.item.name),
- self.filename, self.linenr)
+ self._warn(
+ "'option defconfig_list' set on multiple "
+ "symbols ({0} and {1}). Only {0} will be "
+ "used.".format(self.defconfig_list.name, node.item.name),
+ self.filename,
+ self.linenr,
+ )
elif self._check_token(_T_MODULES):
# To reduce warning spam, only warn if 'option modules' is
@@ -3279,20 +3372,24 @@ class Kconfig(object):
# modules besides the kernel yet, and there it's likely to
# keep being called "MODULES".
if node.item is not self.modules:
- self._warn("the 'modules' option is not supported. "
- "Let me know if this is a problem for you, "
- "as it wouldn't be that hard to implement. "
- "Note that modules are supported -- "
- "Kconfiglib just assumes the symbol name "
- "MODULES, like older versions of the C "
- "implementation did when 'option modules' "
- "wasn't used.",
- self.filename, self.linenr)
+ self._warn(
+ "the 'modules' option is not supported. "
+ "Let me know if this is a problem for you, "
+ "as it wouldn't be that hard to implement. "
+ "Note that modules are supported -- "
+ "Kconfiglib just assumes the symbol name "
+ "MODULES, like older versions of the C "
+ "implementation did when 'option modules' "
+ "wasn't used.",
+ self.filename,
+ self.linenr,
+ )
elif self._check_token(_T_ALLNOCONFIG_Y):
if node.item.__class__ is not Symbol:
- self._parse_error("the 'allnoconfig_y' option is only "
- "valid for symbols")
+ self._parse_error(
+ "the 'allnoconfig_y' option is only " "valid for symbols"
+ )
node.item.is_allnoconfig_y = True
@@ -3315,8 +3412,11 @@ class Kconfig(object):
# UNKNOWN is falsy
if sc.orig_type and sc.orig_type is not new_type:
- self._warn("{} defined with multiple types, {} will be used"
- .format(sc.name_and_loc, TYPE_TO_STR[new_type]))
+ self._warn(
+ "{} defined with multiple types, {} will be used".format(
+ sc.name_and_loc, TYPE_TO_STR[new_type]
+ )
+ )
sc.orig_type = new_type
@@ -3326,8 +3426,10 @@ class Kconfig(object):
# multiple times
if node.prompt:
- self._warn(node.item.name_and_loc +
- " defined with multiple prompts in single location")
+ self._warn(
+ node.item.name_and_loc
+ + " defined with multiple prompts in single location"
+ )
prompt = self._tokens[1]
self._tokens_i = 2
@@ -3336,8 +3438,10 @@ class Kconfig(object):
self._parse_error("expected prompt string")
if prompt != prompt.strip():
- self._warn(node.item.name_and_loc +
- " has leading or trailing whitespace in its prompt")
+ self._warn(
+ node.item.name_and_loc
+ + " has leading or trailing whitespace in its prompt"
+ )
# This avoid issues for e.g. reStructuredText documentation, where
# '*prompt *' is invalid
@@ -3347,8 +3451,10 @@ class Kconfig(object):
def _parse_help(self, node):
if node.help is not None:
- self._warn(node.item.name_and_loc + " defined with more than "
- "one help text -- only the last one will be used")
+ self._warn(
+ node.item.name_and_loc + " defined with more than "
+ "one help text -- only the last one will be used"
+ )
# Micro-optimization. This code is pretty hot.
readline = self._readline
@@ -3403,8 +3509,7 @@ class Kconfig(object):
self._line_after_help(line)
def _empty_help(self, node, line):
- self._warn(node.item.name_and_loc +
- " has 'help' but empty help text")
+ self._warn(node.item.name_and_loc + " has 'help' but empty help text")
node.help = ""
if line:
self._line_after_help(line)
@@ -3447,8 +3552,11 @@ class Kconfig(object):
# Return 'and_expr' directly if we have a "single-operand" OR.
# Otherwise, parse the expression on the right and make an OR node.
# This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))).
- return and_expr if not self._check_token(_T_OR) else \
- (OR, and_expr, self._parse_expr(transform_m))
+ return (
+ and_expr
+ if not self._check_token(_T_OR)
+ else (OR, and_expr, self._parse_expr(transform_m))
+ )
def _parse_and_expr(self, transform_m):
factor = self._parse_factor(transform_m)
@@ -3456,8 +3564,11 @@ class Kconfig(object):
# Return 'factor' directly if we have a "single-operand" AND.
# Otherwise, parse the right operand and make an AND node. This turns
# A && B && C && D into (AND, A, (AND, B, (AND, C, D))).
- return factor if not self._check_token(_T_AND) else \
- (AND, factor, self._parse_and_expr(transform_m))
+ return (
+ factor
+ if not self._check_token(_T_AND)
+ else (AND, factor, self._parse_and_expr(transform_m))
+ )
def _parse_factor(self, transform_m):
token = self._tokens[self._tokens_i]
@@ -3481,8 +3592,7 @@ class Kconfig(object):
# _T_EQUAL, _T_UNEQUAL, etc., deliberately have the same values as
# EQUAL, UNEQUAL, etc., so we can just use the token directly
self._tokens_i += 1
- return (self._tokens[self._tokens_i - 1], token,
- self._expect_sym())
+ return (self._tokens[self._tokens_i - 1], token, self._expect_sym())
if token is _T_NOT:
# token == _T_NOT == NOT
@@ -3689,36 +3799,43 @@ class Kconfig(object):
if cur.item.__class__ in _SYMBOL_CHOICE:
# Propagate 'visible if' and dependencies to the prompt
if cur.prompt:
- cur.prompt = (cur.prompt[0],
- self._make_and(
- cur.prompt[1],
- self._make_and(visible_if, dep)))
+ cur.prompt = (
+ cur.prompt[0],
+ self._make_and(cur.prompt[1], self._make_and(visible_if, dep)),
+ )
# Propagate dependencies to defaults
if cur.defaults:
- cur.defaults = [(default, self._make_and(cond, dep))
- for default, cond in cur.defaults]
+ cur.defaults = [
+ (default, self._make_and(cond, dep))
+ for default, cond in cur.defaults
+ ]
# Propagate dependencies to ranges
if cur.ranges:
- cur.ranges = [(low, high, self._make_and(cond, dep))
- for low, high, cond in cur.ranges]
+ cur.ranges = [
+ (low, high, self._make_and(cond, dep))
+ for low, high, cond in cur.ranges
+ ]
# Propagate dependencies to selects
if cur.selects:
- cur.selects = [(target, self._make_and(cond, dep))
- for target, cond in cur.selects]
+ cur.selects = [
+ (target, self._make_and(cond, dep))
+ for target, cond in cur.selects
+ ]
# Propagate dependencies to implies
if cur.implies:
- cur.implies = [(target, self._make_and(cond, dep))
- for target, cond in cur.implies]
+ cur.implies = [
+ (target, self._make_and(cond, dep))
+ for target, cond in cur.implies
+ ]
elif cur.prompt: # Not a symbol/choice
# Propagate dependencies to the prompt. 'visible if' is only
# propagated to symbols/choices.
- cur.prompt = (cur.prompt[0],
- self._make_and(cur.prompt[1], dep))
+ cur.prompt = (cur.prompt[0], self._make_and(cur.prompt[1], dep))
cur = cur.next
@@ -3744,16 +3861,14 @@ class Kconfig(object):
# Modify the reverse dependencies of the selected symbol
for target, cond in node.selects:
- target.rev_dep = self._make_or(
- target.rev_dep,
- self._make_and(sym, cond))
+ target.rev_dep = self._make_or(target.rev_dep, self._make_and(sym, cond))
# Modify the weak reverse dependencies of the implied
# symbol
for target, cond in node.implies:
target.weak_rev_dep = self._make_or(
- target.weak_rev_dep,
- self._make_and(sym, cond))
+ target.weak_rev_dep, self._make_and(sym, cond)
+ )
#
# Misc.
@@ -3781,82 +3896,106 @@ class Kconfig(object):
for target_sym, _ in sym.selects:
if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN:
- self._warn("{} selects the {} symbol {}, which is not "
- "bool or tristate"
- .format(sym.name_and_loc,
- TYPE_TO_STR[target_sym.orig_type],
- target_sym.name_and_loc))
+ self._warn(
+ "{} selects the {} symbol {}, which is not "
+ "bool or tristate".format(
+ sym.name_and_loc,
+ TYPE_TO_STR[target_sym.orig_type],
+ target_sym.name_and_loc,
+ )
+ )
for target_sym, _ in sym.implies:
if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN:
- self._warn("{} implies the {} symbol {}, which is not "
- "bool or tristate"
- .format(sym.name_and_loc,
- TYPE_TO_STR[target_sym.orig_type],
- target_sym.name_and_loc))
+ self._warn(
+ "{} implies the {} symbol {}, which is not "
+ "bool or tristate".format(
+ sym.name_and_loc,
+ TYPE_TO_STR[target_sym.orig_type],
+ target_sym.name_and_loc,
+ )
+ )
elif sym.orig_type: # STRING/INT/HEX
for default, _ in sym.defaults:
if default.__class__ is not Symbol:
raise KconfigError(
"the {} symbol {} has a malformed default {} -- "
- "expected a single symbol"
- .format(TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc, expr_str(default)))
+ "expected a single symbol".format(
+ TYPE_TO_STR[sym.orig_type],
+ sym.name_and_loc,
+ expr_str(default),
+ )
+ )
if sym.orig_type is STRING:
- if not default.is_constant and not default.nodes and \
- not default.name.isupper():
+ if (
+ not default.is_constant
+ and not default.nodes
+ and not default.name.isupper()
+ ):
# 'default foo' on a string symbol could be either a symbol
# reference or someone leaving out the quotes. Guess that
# the quotes were left out if 'foo' isn't all-uppercase
# (and no symbol named 'foo' exists).
- self._warn("style: quotes recommended around "
- "default value for string symbol "
- + sym.name_and_loc)
+ self._warn(
+ "style: quotes recommended around "
+ "default value for string symbol " + sym.name_and_loc
+ )
elif not num_ok(default, sym.orig_type): # INT/HEX
- self._warn("the {0} symbol {1} has a non-{0} default {2}"
- .format(TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc,
- default.name_and_loc))
+ self._warn(
+ "the {0} symbol {1} has a non-{0} default {2}".format(
+ TYPE_TO_STR[sym.orig_type],
+ sym.name_and_loc,
+ default.name_and_loc,
+ )
+ )
if sym.selects or sym.implies:
- self._warn("the {} symbol {} has selects or implies"
- .format(TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc))
+ self._warn(
+ "the {} symbol {} has selects or implies".format(
+ TYPE_TO_STR[sym.orig_type], sym.name_and_loc
+ )
+ )
else: # UNKNOWN
- self._warn("{} defined without a type"
- .format(sym.name_and_loc))
-
+ self._warn("{} defined without a type".format(sym.name_and_loc))
if sym.ranges:
if sym.orig_type not in _INT_HEX:
self._warn(
- "the {} symbol {} has ranges, but is not int or hex"
- .format(TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc))
+ "the {} symbol {} has ranges, but is not int or hex".format(
+ TYPE_TO_STR[sym.orig_type], sym.name_and_loc
+ )
+ )
else:
for low, high, _ in sym.ranges:
- if not num_ok(low, sym.orig_type) or \
- not num_ok(high, sym.orig_type):
-
- self._warn("the {0} symbol {1} has a non-{0} "
- "range [{2}, {3}]"
- .format(TYPE_TO_STR[sym.orig_type],
- sym.name_and_loc,
- low.name_and_loc,
- high.name_and_loc))
+ if not num_ok(low, sym.orig_type) or not num_ok(
+ high, sym.orig_type
+ ):
+
+ self._warn(
+ "the {0} symbol {1} has a non-{0} "
+ "range [{2}, {3}]".format(
+ TYPE_TO_STR[sym.orig_type],
+ sym.name_and_loc,
+ low.name_and_loc,
+ high.name_and_loc,
+ )
+ )
def _check_choice_sanity(self):
# Checks various choice properties that are handiest to check after
# parsing. Only generates errors and warnings.
def warn_select_imply(sym, expr, expr_type):
- msg = "the choice symbol {} is {} by the following symbols, but " \
- "select/imply has no effect on choice symbols" \
- .format(sym.name_and_loc, expr_type)
+ msg = (
+ "the choice symbol {} is {} by the following symbols, but "
+ "select/imply has no effect on choice symbols".format(
+ sym.name_and_loc, expr_type
+ )
+ )
# si = select/imply
for si in split_expr(expr, OR):
@@ -3866,9 +4005,11 @@ class Kconfig(object):
for choice in self.unique_choices:
if choice.orig_type not in _BOOL_TRISTATE:
- self._warn("{} defined with type {}"
- .format(choice.name_and_loc,
- TYPE_TO_STR[choice.orig_type]))
+ self._warn(
+ "{} defined with type {}".format(
+ choice.name_and_loc, TYPE_TO_STR[choice.orig_type]
+ )
+ )
for node in choice.nodes:
if node.prompt:
@@ -3879,20 +4020,26 @@ class Kconfig(object):
for default, _ in choice.defaults:
if default.__class__ is not Symbol:
raise KconfigError(
- "{} has a malformed default {}"
- .format(choice.name_and_loc, expr_str(default)))
+ "{} has a malformed default {}".format(
+ choice.name_and_loc, expr_str(default)
+ )
+ )
if default.choice is not choice:
- self._warn("the default selection {} of {} is not "
- "contained in the choice"
- .format(default.name_and_loc,
- choice.name_and_loc))
+ self._warn(
+ "the default selection {} of {} is not "
+ "contained in the choice".format(
+ default.name_and_loc, choice.name_and_loc
+ )
+ )
for sym in choice.syms:
if sym.defaults:
- self._warn("default on the choice symbol {} will have "
- "no effect, as defaults do not affect choice "
- "symbols".format(sym.name_and_loc))
+ self._warn(
+ "default on the choice symbol {} will have "
+ "no effect, as defaults do not affect choice "
+ "symbols".format(sym.name_and_loc)
+ )
if sym.rev_dep is not sym.kconfig.n:
warn_select_imply(sym, sym.rev_dep, "selected")
@@ -3903,19 +4050,28 @@ class Kconfig(object):
for node in sym.nodes:
if node.parent.item is choice:
if not node.prompt:
- self._warn("the choice symbol {} has no prompt"
- .format(sym.name_and_loc))
+ self._warn(
+ "the choice symbol {} has no prompt".format(
+ sym.name_and_loc
+ )
+ )
elif node.prompt:
- self._warn("the choice symbol {} is defined with a "
- "prompt outside the choice"
- .format(sym.name_and_loc))
+ self._warn(
+ "the choice symbol {} is defined with a "
+ "prompt outside the choice".format(sym.name_and_loc)
+ )
def _parse_error(self, msg):
- raise KconfigError("{}error: couldn't parse '{}': {}".format(
- "" if self.filename is None else
- "{}:{}: ".format(self.filename, self.linenr),
- self._line.strip(), msg))
+ raise KconfigError(
+ "{}error: couldn't parse '{}': {}".format(
+ ""
+ if self.filename is None
+ else "{}:{}: ".format(self.filename, self.linenr),
+ self._line.strip(),
+ msg,
+ )
+ )
def _trailing_tokens_error(self):
self._parse_error("extra tokens at end of line")
@@ -3954,8 +4110,11 @@ class Kconfig(object):
# - For Python 3, force the encoding. Forcing the encoding on Python 2
# turns strings into Unicode strings, which gets messy. Python 2
# doesn't decode regular strings anyway.
- return open(filename, "rU" if mode == "r" else mode) if _IS_PY2 else \
- open(filename, mode, encoding=self._encoding)
+ return (
+ open(filename, "rU" if mode == "r" else mode)
+ if _IS_PY2
+ else open(filename, mode, encoding=self._encoding)
+ )
def _check_undef_syms(self):
# Prints warnings for all references to undefined symbols within the
@@ -3992,14 +4151,14 @@ class Kconfig(object):
# symbols, but shouldn't be flagged
#
# - The MODULES symbol always exists
- if not sym.nodes and not is_num(sym.name) and \
- sym.name != "MODULES":
+ if not sym.nodes and not is_num(sym.name) and sym.name != "MODULES":
msg = "undefined symbol {}:".format(sym.name)
for node in self.node_iter():
if sym in node.referenced:
- msg += "\n\n- Referenced at {}:{}:\n\n{}" \
- .format(node.filename, node.linenr, node)
+ msg += "\n\n- Referenced at {}:{}:\n\n{}".format(
+ node.filename, node.linenr, node
+ )
self._warn(msg)
def _warn(self, msg, filename=None, linenr=None):
@@ -4274,6 +4433,7 @@ class Symbol(object):
kconfig:
The Kconfig instance this symbol is from.
"""
+
__slots__ = (
"_cached_assignable",
"_cached_str_val",
@@ -4311,9 +4471,11 @@ class Symbol(object):
"""
See the class documentation.
"""
- if self.orig_type is TRISTATE and \
- (self.choice and self.choice.tri_value == 2 or
- not self.kconfig.modules.tri_value):
+ if self.orig_type is TRISTATE and (
+ self.choice
+ and self.choice.tri_value == 2
+ or not self.kconfig.modules.tri_value
+ ):
return BOOL
@@ -4344,7 +4506,7 @@ class Symbol(object):
# function call (property magic)
vis = self.visibility
- self._write_to_conf = (vis != 0)
+ self._write_to_conf = vis != 0
if self.orig_type in _INT_HEX:
# The C implementation checks the user value against the range in a
@@ -4361,10 +4523,16 @@ class Symbol(object):
# The zeros are from the C implementation running strtoll()
# on empty strings
- low = int(low_expr.str_value, base) if \
- _is_base_n(low_expr.str_value, base) else 0
- high = int(high_expr.str_value, base) if \
- _is_base_n(high_expr.str_value, base) else 0
+ low = (
+ int(low_expr.str_value, base)
+ if _is_base_n(low_expr.str_value, base)
+ else 0
+ )
+ high = (
+ int(high_expr.str_value, base)
+ if _is_base_n(high_expr.str_value, base)
+ else 0
+ )
break
else:
@@ -4381,10 +4549,14 @@ class Symbol(object):
self.kconfig._warn(
"user value {} on the {} symbol {} ignored due to "
"being outside the active range ([{}, {}]) -- falling "
- "back on defaults"
- .format(num2str(user_val), TYPE_TO_STR[self.orig_type],
- self.name_and_loc,
- num2str(low), num2str(high)))
+ "back on defaults".format(
+ num2str(user_val),
+ TYPE_TO_STR[self.orig_type],
+ self.name_and_loc,
+ num2str(low),
+ num2str(high),
+ )
+ )
else:
# If the user value is well-formed and satisfies range
# contraints, it is stored in exactly the same form as
@@ -4424,18 +4596,20 @@ class Symbol(object):
if clamp is not None:
# The value is rewritten to a standard form if it is
# clamped
- val = str(clamp) \
- if self.orig_type is INT else \
- hex(clamp)
+ val = str(clamp) if self.orig_type is INT else hex(clamp)
if has_default:
num2str = str if base == 10 else hex
self.kconfig._warn(
"default value {} on {} clamped to {} due to "
- "being outside the active range ([{}, {}])"
- .format(val_num, self.name_and_loc,
- num2str(clamp), num2str(low),
- num2str(high)))
+ "being outside the active range ([{}, {}])".format(
+ val_num,
+ self.name_and_loc,
+ num2str(clamp),
+ num2str(low),
+ num2str(high),
+ )
+ )
elif self.orig_type is STRING:
if vis and self.user_value is not None:
@@ -4473,8 +4647,10 @@ class Symbol(object):
# Would take some work to give the location here
self.kconfig._warn(
"The {} symbol {} is being evaluated in a logical context "
- "somewhere. It will always evaluate to n."
- .format(TYPE_TO_STR[self.orig_type], self.name_and_loc))
+ "somewhere. It will always evaluate to n.".format(
+ TYPE_TO_STR[self.orig_type], self.name_and_loc
+ )
+ )
self._cached_tri_val = 0
return 0
@@ -4482,7 +4658,7 @@ class Symbol(object):
# Warning: See Symbol._rec_invalidate(), and note that this is a hidden
# function call (property magic)
vis = self.visibility
- self._write_to_conf = (vis != 0)
+ self._write_to_conf = vis != 0
val = 0
@@ -4523,8 +4699,7 @@ class Symbol(object):
# m is promoted to y for (1) bool symbols and (2) symbols with a
# weak_rev_dep (from imply) of y
- if val == 1 and \
- (self.type is BOOL or expr_value(self.weak_rev_dep) == 2):
+ if val == 1 and (self.type is BOOL or expr_value(self.weak_rev_dep) == 2):
val = 2
elif vis == 2:
@@ -4570,19 +4745,17 @@ class Symbol(object):
return ""
if self.orig_type in _BOOL_TRISTATE:
- return "{}{}={}\n" \
- .format(self.kconfig.config_prefix, self.name, val) \
- if val != "n" else \
- "# {}{} is not set\n" \
- .format(self.kconfig.config_prefix, self.name)
+ return (
+ "{}{}={}\n".format(self.kconfig.config_prefix, self.name, val)
+ if val != "n"
+ else "# {}{} is not set\n".format(self.kconfig.config_prefix, self.name)
+ )
if self.orig_type in _INT_HEX:
- return "{}{}={}\n" \
- .format(self.kconfig.config_prefix, self.name, val)
+ return "{}{}={}\n".format(self.kconfig.config_prefix, self.name, val)
# sym.orig_type is STRING
- return '{}{}="{}"\n' \
- .format(self.kconfig.config_prefix, self.name, escape(val))
+ return '{}{}="{}"\n'.format(self.kconfig.config_prefix, self.name, escape(val))
@property
def name_and_loc(self):
@@ -4646,21 +4819,31 @@ class Symbol(object):
return True
# Check if the value is valid for our type
- if not (self.orig_type is BOOL and value in (2, 0) or
- self.orig_type is TRISTATE and value in TRI_TO_STR or
- value.__class__ is str and
- (self.orig_type is STRING or
- self.orig_type is INT and _is_base_n(value, 10) or
- self.orig_type is HEX and _is_base_n(value, 16)
- and int(value, 16) >= 0)):
+ if not (
+ self.orig_type is BOOL
+ and value in (2, 0)
+ or self.orig_type is TRISTATE
+ and value in TRI_TO_STR
+ or value.__class__ is str
+ and (
+ self.orig_type is STRING
+ or self.orig_type is INT
+ and _is_base_n(value, 10)
+ or self.orig_type is HEX
+ and _is_base_n(value, 16)
+ and int(value, 16) >= 0
+ )
+ ):
# Display tristate values as n, m, y in the warning
self.kconfig._warn(
"the value {} is invalid for {}, which has type {} -- "
- "assignment ignored"
- .format(TRI_TO_STR[value] if value in TRI_TO_STR else
- "'{}'".format(value),
- self.name_and_loc, TYPE_TO_STR[self.orig_type]))
+ "assignment ignored".format(
+ TRI_TO_STR[value] if value in TRI_TO_STR else "'{}'".format(value),
+ self.name_and_loc,
+ TYPE_TO_STR[self.orig_type],
+ )
+ )
return False
@@ -4738,17 +4921,28 @@ class Symbol(object):
add('"{}"'.format(node.prompt[0]))
# Only add quotes for non-bool/tristate symbols
- add("value " + (self.str_value if self.orig_type in _BOOL_TRISTATE
- else '"{}"'.format(self.str_value)))
+ add(
+ "value "
+ + (
+ self.str_value
+ if self.orig_type in _BOOL_TRISTATE
+ else '"{}"'.format(self.str_value)
+ )
+ )
if not self.is_constant:
# These aren't helpful to show for constant symbols
if self.user_value is not None:
# Only add quotes for non-bool/tristate symbols
- add("user value " + (TRI_TO_STR[self.user_value]
- if self.orig_type in _BOOL_TRISTATE
- else '"{}"'.format(self.user_value)))
+ add(
+ "user value "
+ + (
+ TRI_TO_STR[self.user_value]
+ if self.orig_type in _BOOL_TRISTATE
+ else '"{}"'.format(self.user_value)
+ )
+ )
add("visibility " + TRI_TO_STR[self.visibility])
@@ -4798,8 +4992,7 @@ class Symbol(object):
Works like Symbol.__str__(), but allows a custom format to be used for
all symbol/choice references. See expr_str().
"""
- return "\n\n".join(node.custom_str(sc_expr_str_fn)
- for node in self.nodes)
+ return "\n\n".join(node.custom_str(sc_expr_str_fn) for node in self.nodes)
#
# Private methods
@@ -4830,18 +5023,18 @@ class Symbol(object):
self.implies = []
self.ranges = []
- self.user_value = \
- self.choice = \
- self.env_var = \
- self._cached_str_val = self._cached_tri_val = self._cached_vis = \
- self._cached_assignable = None
+ self.user_value = (
+ self.choice
+ ) = (
+ self.env_var
+ ) = (
+ self._cached_str_val
+ ) = self._cached_tri_val = self._cached_vis = self._cached_assignable = None
# _write_to_conf is calculated along with the value. If True, the
# Symbol gets a .config entry.
- self.is_allnoconfig_y = \
- self._was_set = \
- self._write_to_conf = False
+ self.is_allnoconfig_y = self._was_set = self._write_to_conf = False
# See Kconfig._build_dep()
self._dependents = set()
@@ -4895,8 +5088,9 @@ class Symbol(object):
def _invalidate(self):
# Marks the symbol as needing to be recalculated
- self._cached_str_val = self._cached_tri_val = self._cached_vis = \
- self._cached_assignable = None
+ self._cached_str_val = (
+ self._cached_tri_val
+ ) = self._cached_vis = self._cached_assignable = None
def _rec_invalidate(self):
# Invalidates the symbol and all items that (possibly) depend on it
@@ -4948,8 +5142,10 @@ class Symbol(object):
return
if self.kconfig._warn_assign_no_prompt:
- self.kconfig._warn(self.name_and_loc + " has no prompt, meaning "
- "user values have no effect on it")
+ self.kconfig._warn(
+ self.name_and_loc + " has no prompt, meaning "
+ "user values have no effect on it"
+ )
def _str_default(self):
# write_min_config() helper function. Returns the value the symbol
@@ -4968,9 +5164,7 @@ class Symbol(object):
val = min(expr_value(default), cond_val)
break
- val = max(expr_value(self.rev_dep),
- expr_value(self.weak_rev_dep),
- val)
+ val = max(expr_value(self.rev_dep), expr_value(self.weak_rev_dep), val)
# Transpose mod to yes if type is bool (possibly due to modules
# being disabled)
@@ -4992,11 +5186,15 @@ class Symbol(object):
# and menus) is selected by some other symbol. Also warn if a symbol
# whose direct dependencies evaluate to m is selected to y.
- msg = "{} has direct dependencies {} with value {}, but is " \
- "currently being {}-selected by the following symbols:" \
- .format(self.name_and_loc, expr_str(self.direct_dep),
- TRI_TO_STR[expr_value(self.direct_dep)],
- TRI_TO_STR[expr_value(self.rev_dep)])
+ msg = (
+ "{} has direct dependencies {} with value {}, but is "
+ "currently being {}-selected by the following symbols:".format(
+ self.name_and_loc,
+ expr_str(self.direct_dep),
+ TRI_TO_STR[expr_value(self.direct_dep)],
+ TRI_TO_STR[expr_value(self.rev_dep)],
+ )
+ )
# The reverse dependencies from each select are ORed together
for select in split_expr(self.rev_dep, OR):
@@ -5010,17 +5208,20 @@ class Symbol(object):
# In both cases, we can split on AND and pick the first operand
selecting_sym = split_expr(select, AND)[0]
- msg += "\n - {}, with value {}, direct dependencies {} " \
- "(value: {})" \
- .format(selecting_sym.name_and_loc,
- selecting_sym.str_value,
- expr_str(selecting_sym.direct_dep),
- TRI_TO_STR[expr_value(selecting_sym.direct_dep)])
+ msg += (
+ "\n - {}, with value {}, direct dependencies {} "
+ "(value: {})".format(
+ selecting_sym.name_and_loc,
+ selecting_sym.str_value,
+ expr_str(selecting_sym.direct_dep),
+ TRI_TO_STR[expr_value(selecting_sym.direct_dep)],
+ )
+ )
if select.__class__ is tuple:
- msg += ", and select condition {} (value: {})" \
- .format(expr_str(select[2]),
- TRI_TO_STR[expr_value(select[2])])
+ msg += ", and select condition {} (value: {})".format(
+ expr_str(select[2]), TRI_TO_STR[expr_value(select[2])]
+ )
self.kconfig._warn(msg)
@@ -5182,6 +5383,7 @@ class Choice(object):
kconfig:
The Kconfig instance this choice is from.
"""
+
__slots__ = (
"_cached_assignable",
"_cached_selection",
@@ -5299,16 +5501,22 @@ class Choice(object):
self._was_set = True
return True
- if not (self.orig_type is BOOL and value in (2, 0) or
- self.orig_type is TRISTATE and value in TRI_TO_STR):
+ if not (
+ self.orig_type is BOOL
+ and value in (2, 0)
+ or self.orig_type is TRISTATE
+ and value in TRI_TO_STR
+ ):
# Display tristate values as n, m, y in the warning
self.kconfig._warn(
"the value {} is invalid for {}, which has type {} -- "
- "assignment ignored"
- .format(TRI_TO_STR[value] if value in TRI_TO_STR else
- "'{}'".format(value),
- self.name_and_loc, TYPE_TO_STR[self.orig_type]))
+ "assignment ignored".format(
+ TRI_TO_STR[value] if value in TRI_TO_STR else "'{}'".format(value),
+ self.name_and_loc,
+ TYPE_TO_STR[self.orig_type],
+ )
+ )
return False
@@ -5346,8 +5554,10 @@ class Choice(object):
Returns a string with information about the choice when it is evaluated
on e.g. the interactive Python prompt.
"""
- fields = ["choice " + self.name if self.name else "choice",
- TYPE_TO_STR[self.type]]
+ fields = [
+ "choice " + self.name if self.name else "choice",
+ TYPE_TO_STR[self.type],
+ ]
add = fields.append
for node in self.nodes:
@@ -5357,14 +5567,13 @@ class Choice(object):
add("mode " + self.str_value)
if self.user_value is not None:
- add('user mode {}'.format(TRI_TO_STR[self.user_value]))
+ add("user mode {}".format(TRI_TO_STR[self.user_value]))
if self.selection:
add("{} selected".format(self.selection.name))
if self.user_selection:
- user_sel_str = "{} selected by user" \
- .format(self.user_selection.name)
+ user_sel_str = "{} selected by user".format(self.user_selection.name)
if self.selection is not self.user_selection:
user_sel_str += " (overridden)"
@@ -5399,8 +5608,7 @@ class Choice(object):
Works like Choice.__str__(), but allows a custom format to be used for
all symbol/choice references. See expr_str().
"""
- return "\n\n".join(node.custom_str(sc_expr_str_fn)
- for node in self.nodes)
+ return "\n\n".join(node.custom_str(sc_expr_str_fn) for node in self.nodes)
#
# Private methods
@@ -5425,9 +5633,9 @@ class Choice(object):
self.syms = []
self.defaults = []
- self.name = \
- self.user_value = self.user_selection = \
- self._cached_vis = self._cached_assignable = None
+ self.name = (
+ self.user_value
+ ) = self.user_selection = self._cached_vis = self._cached_assignable = None
self._cached_selection = _NO_CACHED_SELECTION
@@ -5644,6 +5852,7 @@ class MenuNode(object):
kconfig:
The Kconfig instance the menu node is from.
"""
+
__slots__ = (
"dep",
"filename",
@@ -5658,7 +5867,6 @@ class MenuNode(object):
"parent",
"prompt",
"visibility",
-
# Properties
"defaults",
"selects",
@@ -5689,32 +5897,28 @@ class MenuNode(object):
"""
See the class documentation.
"""
- return [(default, self._strip_dep(cond))
- for default, cond in self.defaults]
+ return [(default, self._strip_dep(cond)) for default, cond in self.defaults]
@property
def orig_selects(self):
"""
See the class documentation.
"""
- return [(select, self._strip_dep(cond))
- for select, cond in self.selects]
+ return [(select, self._strip_dep(cond)) for select, cond in self.selects]
@property
def orig_implies(self):
"""
See the class documentation.
"""
- return [(imply, self._strip_dep(cond))
- for imply, cond in self.implies]
+ return [(imply, self._strip_dep(cond)) for imply, cond in self.implies]
@property
def orig_ranges(self):
"""
See the class documentation.
"""
- return [(low, high, self._strip_dep(cond))
- for low, high, cond in self.ranges]
+ return [(low, high, self._strip_dep(cond)) for low, high, cond in self.ranges]
@property
def referenced(self):
@@ -5774,8 +5978,11 @@ class MenuNode(object):
add("menu node for comment")
if self.prompt:
- add('prompt "{}" (visibility {})'.format(
- self.prompt[0], TRI_TO_STR[expr_value(self.prompt[1])]))
+ add(
+ 'prompt "{}" (visibility {})'.format(
+ self.prompt[0], TRI_TO_STR[expr_value(self.prompt[1])]
+ )
+ )
if self.item.__class__ is Symbol and self.is_menuconfig:
add("is menuconfig")
@@ -5822,20 +6029,20 @@ class MenuNode(object):
Works like MenuNode.__str__(), but allows a custom format to be used
for all symbol/choice references. See expr_str().
"""
- return self._menu_comment_node_str(sc_expr_str_fn) \
- if self.item in _MENU_COMMENT else \
- self._sym_choice_node_str(sc_expr_str_fn)
+ return (
+ self._menu_comment_node_str(sc_expr_str_fn)
+ if self.item in _MENU_COMMENT
+ else self._sym_choice_node_str(sc_expr_str_fn)
+ )
def _menu_comment_node_str(self, sc_expr_str_fn):
- s = '{} "{}"'.format("menu" if self.item is MENU else "comment",
- self.prompt[0])
+ s = '{} "{}"'.format("menu" if self.item is MENU else "comment", self.prompt[0])
if self.dep is not self.kconfig.y:
s += "\n\tdepends on {}".format(expr_str(self.dep, sc_expr_str_fn))
if self.item is MENU and self.visibility is not self.kconfig.y:
- s += "\n\tvisible if {}".format(expr_str(self.visibility,
- sc_expr_str_fn))
+ s += "\n\tvisible if {}".format(expr_str(self.visibility, sc_expr_str_fn))
return s
@@ -5851,8 +6058,7 @@ class MenuNode(object):
sc = self.item
if sc.__class__ is Symbol:
- lines = [("menuconfig " if self.is_menuconfig else "config ")
- + sc.name]
+ lines = [("menuconfig " if self.is_menuconfig else "config ") + sc.name]
else:
lines = ["choice " + sc.name if sc.name else "choice"]
@@ -5868,8 +6074,9 @@ class MenuNode(object):
# Symbol defined without a type (which generates a warning)
prefix = "prompt"
- indent_add_cond(prefix + ' "{}"'.format(escape(self.prompt[0])),
- self.orig_prompt[1])
+ indent_add_cond(
+ prefix + ' "{}"'.format(escape(self.prompt[0])), self.orig_prompt[1]
+ )
if sc.__class__ is Symbol:
if sc.is_allnoconfig_y:
@@ -5886,13 +6093,12 @@ class MenuNode(object):
for low, high, cond in self.orig_ranges:
indent_add_cond(
- "range {} {}".format(sc_expr_str_fn(low),
- sc_expr_str_fn(high)),
- cond)
+ "range {} {}".format(sc_expr_str_fn(low), sc_expr_str_fn(high)),
+ cond,
+ )
for default, cond in self.orig_defaults:
- indent_add_cond("default " + expr_str(default, sc_expr_str_fn),
- cond)
+ indent_add_cond("default " + expr_str(default, sc_expr_str_fn), cond)
if sc.__class__ is Choice and sc.is_optional:
indent_add("optional")
@@ -5954,6 +6160,7 @@ class Variable(object):
is_recursive:
True if the variable is recursive (defined with =).
"""
+
__slots__ = (
"_n_expansions",
"is_recursive",
@@ -5979,10 +6186,9 @@ class Variable(object):
return self.kconfig._fn_val((self.name,) + args)
def __repr__(self):
- return "<variable {}, {}, value '{}'>" \
- .format(self.name,
- "recursive" if self.is_recursive else "immediate",
- self.value)
+ return "<variable {}, {}, value '{}'>".format(
+ self.name, "recursive" if self.is_recursive else "immediate", self.value
+ )
class KconfigError(Exception):
@@ -5993,6 +6199,7 @@ class KconfigError(Exception):
KconfigSyntaxError alias is only maintained for backwards compatibility.
"""
+
KconfigSyntaxError = KconfigError # Backwards compatibility
@@ -6010,7 +6217,8 @@ class _KconfigIOError(IOError):
def __init__(self, ioerror, msg):
self.msg = msg
super(_KconfigIOError, self).__init__(
- ioerror.errno, ioerror.strerror, ioerror.filename)
+ ioerror.errno, ioerror.strerror, ioerror.filename
+ )
def __str__(self):
return self.msg
@@ -6070,12 +6278,19 @@ def expr_value(expr):
# parse as numbers
comp = _strcmp(v1.str_value, v2.str_value)
- return 2*(comp == 0 if rel is EQUAL else
- comp != 0 if rel is UNEQUAL else
- comp < 0 if rel is LESS else
- comp <= 0 if rel is LESS_EQUAL else
- comp > 0 if rel is GREATER else
- comp >= 0)
+ return 2 * (
+ comp == 0
+ if rel is EQUAL
+ else comp != 0
+ if rel is UNEQUAL
+ else comp < 0
+ if rel is LESS
+ else comp <= 0
+ if rel is LESS_EQUAL
+ else comp > 0
+ if rel is GREATER
+ else comp >= 0
+ )
def standard_sc_expr_str(sc):
@@ -6115,14 +6330,18 @@ def expr_str(expr, sc_expr_str_fn=standard_sc_expr_str):
return sc_expr_str_fn(expr)
if expr[0] is AND:
- return "{} && {}".format(_parenthesize(expr[1], OR, sc_expr_str_fn),
- _parenthesize(expr[2], OR, sc_expr_str_fn))
+ return "{} && {}".format(
+ _parenthesize(expr[1], OR, sc_expr_str_fn),
+ _parenthesize(expr[2], OR, sc_expr_str_fn),
+ )
if expr[0] is OR:
# This turns A && B || C && D into "(A && B) || (C && D)", which is
# redundant, but more readable
- return "{} || {}".format(_parenthesize(expr[1], AND, sc_expr_str_fn),
- _parenthesize(expr[2], AND, sc_expr_str_fn))
+ return "{} || {}".format(
+ _parenthesize(expr[1], AND, sc_expr_str_fn),
+ _parenthesize(expr[2], AND, sc_expr_str_fn),
+ )
if expr[0] is NOT:
if expr[1].__class__ is tuple:
@@ -6133,8 +6352,9 @@ def expr_str(expr, sc_expr_str_fn=standard_sc_expr_str):
#
# Relation operands are always symbols (quoted strings are constant
# symbols)
- return "{} {} {}".format(sc_expr_str_fn(expr[1]), REL_TO_STR[expr[0]],
- sc_expr_str_fn(expr[2]))
+ return "{} {} {}".format(
+ sc_expr_str_fn(expr[1]), REL_TO_STR[expr[0]], sc_expr_str_fn(expr[2])
+ )
def expr_items(expr):
@@ -6216,7 +6436,7 @@ def escape(s):
replaced by \" and \\, respectively.
"""
# \ must be escaped before " to avoid double escaping
- return s.replace("\\", r"\\").replace('"', r'\"')
+ return s.replace("\\", r"\\").replace('"', r"\"")
def unescape(s):
@@ -6226,6 +6446,7 @@ def unescape(s):
"""
return _unescape_sub(r"\1", s)
+
# unescape() helper
_unescape_sub = re.compile(r"\\(.)").sub
@@ -6245,15 +6466,16 @@ def standard_kconfig(description=None):
import argparse
parser = argparse.ArgumentParser(
- formatter_class=argparse.RawDescriptionHelpFormatter,
- description=description)
+ formatter_class=argparse.RawDescriptionHelpFormatter, description=description
+ )
parser.add_argument(
"kconfig",
metavar="KCONFIG",
default="Kconfig",
nargs="?",
- help="Top-level Kconfig file (default: Kconfig)")
+ help="Top-level Kconfig file (default: Kconfig)",
+ )
return Kconfig(parser.parse_args().kconfig, suppress_traceback=True)
@@ -6299,16 +6521,20 @@ def load_allconfig(kconf, filename):
try:
print(kconf.load_config("all.config", False))
except EnvironmentError as e2:
- sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} "
- "nor all.config could be opened: {}, {}"
- .format(filename, std_msg(e1), std_msg(e2)))
+ sys.exit(
+ "error: KCONFIG_ALLCONFIG is set, but neither {} "
+ "nor all.config could be opened: {}, {}".format(
+ filename, std_msg(e1), std_msg(e2)
+ )
+ )
else:
try:
print(kconf.load_config(allconfig, False))
except EnvironmentError as e:
- sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which "
- "could not be opened: {}"
- .format(allconfig, std_msg(e)))
+ sys.exit(
+ "error: KCONFIG_ALLCONFIG is set to '{}', which "
+ "could not be opened: {}".format(allconfig, std_msg(e))
+ )
kconf.warn_assign_override = old_warn_assign_override
kconf.warn_assign_redun = old_warn_assign_redun
@@ -6332,8 +6558,11 @@ def _visibility(sc):
vis = max(vis, expr_value(node.prompt[1]))
if sc.__class__ is Symbol and sc.choice:
- if sc.choice.orig_type is TRISTATE and \
- sc.orig_type is not TRISTATE and sc.choice.tri_value != 2:
+ if (
+ sc.choice.orig_type is TRISTATE
+ and sc.orig_type is not TRISTATE
+ and sc.choice.tri_value != 2
+ ):
# Non-tristate choice symbols are only visible in y mode
return 0
@@ -6407,8 +6636,11 @@ def _sym_to_num(sym):
# For BOOL and TRISTATE, n/m/y count as 0/1/2. This mirrors 9059a3493ef
# ("kconfig: fix relational operators for bool and tristate symbols") in
# the C implementation.
- return sym.tri_value if sym.orig_type in _BOOL_TRISTATE else \
- int(sym.str_value, _TYPE_TO_BASE[sym.orig_type])
+ return (
+ sym.tri_value
+ if sym.orig_type in _BOOL_TRISTATE
+ else int(sym.str_value, _TYPE_TO_BASE[sym.orig_type])
+ )
def _touch_dep_file(path, sym_name):
@@ -6421,8 +6653,7 @@ def _touch_dep_file(path, sym_name):
os.makedirs(sym_path_dir, 0o755)
# A kind of truncating touch, mirroring the C tools
- os.close(os.open(
- sym_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644))
+ os.close(os.open(sym_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644))
def _save_old(path):
@@ -6431,6 +6662,7 @@ def _save_old(path):
def copy(src, dst):
# Import as needed, to save some startup time
import shutil
+
shutil.copyfile(src, dst)
if islink(path):
@@ -6463,8 +6695,8 @@ def _locs(sc):
if sc.nodes:
return "(defined at {})".format(
- ", ".join("{0.filename}:{0.linenr}".format(node)
- for node in sc.nodes))
+ ", ".join("{0.filename}:{0.linenr}".format(node) for node in sc.nodes)
+ )
return "(undefined)"
@@ -6491,13 +6723,13 @@ def _expr_depends_on(expr, sym):
elif left is not sym:
return False
- return (expr[0] is EQUAL and right is sym.kconfig.m or
- right is sym.kconfig.y) or \
- (expr[0] is UNEQUAL and right is sym.kconfig.n)
+ return (
+ expr[0] is EQUAL and right is sym.kconfig.m or right is sym.kconfig.y
+ ) or (expr[0] is UNEQUAL and right is sym.kconfig.n)
- return expr[0] is AND and \
- (_expr_depends_on(expr[1], sym) or
- _expr_depends_on(expr[2], sym))
+ return expr[0] is AND and (
+ _expr_depends_on(expr[1], sym) or _expr_depends_on(expr[2], sym)
+ )
def _auto_menu_dep(node1, node2):
@@ -6505,8 +6737,7 @@ def _auto_menu_dep(node1, node2):
# node2 has a prompt, we check its condition. Otherwise, we look directly
# at node2.dep.
- return _expr_depends_on(node2.prompt[1] if node2.prompt else node2.dep,
- node1.item)
+ return _expr_depends_on(node2.prompt[1] if node2.prompt else node2.dep, node1.item)
def _flatten(node):
@@ -6521,8 +6752,7 @@ def _flatten(node):
# you enter the choice at some location with a prompt.
while node:
- if node.list and not node.prompt and \
- node.item.__class__ is not Choice:
+ if node.list and not node.prompt and node.item.__class__ is not Choice:
last_node = node.list
while 1:
@@ -6637,9 +6867,11 @@ def _check_dep_loop_sym(sym, ignore_choice):
#
# Since we aren't entering the choice via a choice symbol, all
# choice symbols need to be checked, hence the None.
- loop = _check_dep_loop_choice(dep, None) \
- if dep.__class__ is Choice \
- else _check_dep_loop_sym(dep, False)
+ loop = (
+ _check_dep_loop_choice(dep, None)
+ if dep.__class__ is Choice
+ else _check_dep_loop_sym(dep, False)
+ )
if loop:
# Dependency loop found
@@ -6711,8 +6943,7 @@ def _found_dep_loop(loop, cur):
# Yep, we have the entire loop. Throw an exception that shows it.
- msg = "\nDependency loop\n" \
- "===============\n\n"
+ msg = "\nDependency loop\n" "===============\n\n"
for item in loop:
if item is not loop[0]:
@@ -6720,8 +6951,7 @@ def _found_dep_loop(loop, cur):
if item.__class__ is Symbol and item.choice:
msg += "the choice symbol "
- msg += "{}, with definition...\n\n{}\n\n" \
- .format(item.name_and_loc, item)
+ msg += "{}, with definition...\n\n{}\n\n".format(item.name_and_loc, item)
# Small wart: Since we reuse the already calculated
# Symbol/Choice._dependents sets for recursive dependency detection, we
@@ -6738,12 +6968,14 @@ def _found_dep_loop(loop, cur):
if item.__class__ is Symbol:
if item.rev_dep is not item.kconfig.n:
- msg += "(select-related dependencies: {})\n\n" \
- .format(expr_str(item.rev_dep))
+ msg += "(select-related dependencies: {})\n\n".format(
+ expr_str(item.rev_dep)
+ )
if item.weak_rev_dep is not item.kconfig.n:
- msg += "(imply-related dependencies: {})\n\n" \
- .format(expr_str(item.rev_dep))
+ msg += "(imply-related dependencies: {})\n\n".format(
+ expr_str(item.rev_dep)
+ )
msg += "...depends again on " + loop[0].name_and_loc
@@ -6765,11 +6997,14 @@ def _decoding_error(e, filename, macro_linenr=None):
"Problematic data: {}\n"
"Reason: {}".format(
e.encoding,
- "'{}'".format(filename) if macro_linenr is None else
- "output from macro at {}:{}".format(filename, macro_linenr),
- e.object[max(e.start - 40, 0):e.end + 40],
- e.object[e.start:e.end],
- e.reason))
+ "'{}'".format(filename)
+ if macro_linenr is None
+ else "output from macro at {}:{}".format(filename, macro_linenr),
+ e.object[max(e.start - 40, 0) : e.end + 40],
+ e.object[e.start : e.end],
+ e.reason,
+ )
+ )
def _warn_verbose_deprecated(fn_name):
@@ -6779,7 +7014,8 @@ def _warn_verbose_deprecated(fn_name):
"and is always generated. Do e.g. print(kconf.{0}()) if you want to "
"want to show a message like \"Loaded configuration '.config'\" on "
"stdout. The old API required ugly hacks to reuse messages in "
- "configuration interfaces.\n".format(fn_name))
+ "configuration interfaces.\n".format(fn_name)
+ )
# Predefined preprocessor functions
@@ -6808,8 +7044,7 @@ def _warning_if_fn(kconf, _, cond, msg):
def _error_if_fn(kconf, _, cond, msg):
if cond == "y":
- raise KconfigError("{}:{}: {}".format(
- kconf.filename, kconf.linenr, msg))
+ raise KconfigError("{}:{}: {}".format(kconf.filename, kconf.linenr, msg))
return ""
@@ -6829,9 +7064,11 @@ def _shell_fn(kconf, _, command):
_decoding_error(e, kconf.filename, kconf.linenr)
if stderr:
- kconf._warn("'{}' wrote to stderr: {}".format(
- command, "\n".join(stderr.splitlines())),
- kconf.filename, kconf.linenr)
+ kconf._warn(
+ "'{}' wrote to stderr: {}".format(command, "\n".join(stderr.splitlines())),
+ kconf.filename,
+ kconf.linenr,
+ )
# Universal newlines with splitlines() (to prevent e.g. stray \r's in
# command output on Windows), trailing newline removal, and
@@ -6842,6 +7079,7 @@ def _shell_fn(kconf, _, command):
# parameter was added in 3.6), so we do this manual version instead.
return "\n".join(stdout.splitlines()).rstrip("\n").replace("\n", " ")
+
#
# Global constants
#
@@ -6871,6 +7109,7 @@ try:
except AttributeError:
# Only import as needed, to save some startup time
import platform
+
_UNAME_RELEASE = platform.uname()[2]
# The token and type constants below are safe to test with 'is', which is a bit
@@ -6940,112 +7179,112 @@ except AttributeError:
# Keyword to token map, with the get() method assigned directly as a small
# optimization
_get_keyword = {
- "---help---": _T_HELP,
- "allnoconfig_y": _T_ALLNOCONFIG_Y,
- "bool": _T_BOOL,
- "boolean": _T_BOOL,
- "choice": _T_CHOICE,
- "comment": _T_COMMENT,
- "config": _T_CONFIG,
- "def_bool": _T_DEF_BOOL,
- "def_hex": _T_DEF_HEX,
- "def_int": _T_DEF_INT,
- "def_string": _T_DEF_STRING,
- "def_tristate": _T_DEF_TRISTATE,
- "default": _T_DEFAULT,
+ "---help---": _T_HELP,
+ "allnoconfig_y": _T_ALLNOCONFIG_Y,
+ "bool": _T_BOOL,
+ "boolean": _T_BOOL,
+ "choice": _T_CHOICE,
+ "comment": _T_COMMENT,
+ "config": _T_CONFIG,
+ "def_bool": _T_DEF_BOOL,
+ "def_hex": _T_DEF_HEX,
+ "def_int": _T_DEF_INT,
+ "def_string": _T_DEF_STRING,
+ "def_tristate": _T_DEF_TRISTATE,
+ "default": _T_DEFAULT,
"defconfig_list": _T_DEFCONFIG_LIST,
- "depends": _T_DEPENDS,
- "endchoice": _T_ENDCHOICE,
- "endif": _T_ENDIF,
- "endmenu": _T_ENDMENU,
- "env": _T_ENV,
- "grsource": _T_ORSOURCE, # Backwards compatibility
- "gsource": _T_OSOURCE, # Backwards compatibility
- "help": _T_HELP,
- "hex": _T_HEX,
- "if": _T_IF,
- "imply": _T_IMPLY,
- "int": _T_INT,
- "mainmenu": _T_MAINMENU,
- "menu": _T_MENU,
- "menuconfig": _T_MENUCONFIG,
- "modules": _T_MODULES,
- "on": _T_ON,
- "option": _T_OPTION,
- "optional": _T_OPTIONAL,
- "orsource": _T_ORSOURCE,
- "osource": _T_OSOURCE,
- "prompt": _T_PROMPT,
- "range": _T_RANGE,
- "rsource": _T_RSOURCE,
- "select": _T_SELECT,
- "source": _T_SOURCE,
- "string": _T_STRING,
- "tristate": _T_TRISTATE,
- "visible": _T_VISIBLE,
+ "depends": _T_DEPENDS,
+ "endchoice": _T_ENDCHOICE,
+ "endif": _T_ENDIF,
+ "endmenu": _T_ENDMENU,
+ "env": _T_ENV,
+ "grsource": _T_ORSOURCE, # Backwards compatibility
+ "gsource": _T_OSOURCE, # Backwards compatibility
+ "help": _T_HELP,
+ "hex": _T_HEX,
+ "if": _T_IF,
+ "imply": _T_IMPLY,
+ "int": _T_INT,
+ "mainmenu": _T_MAINMENU,
+ "menu": _T_MENU,
+ "menuconfig": _T_MENUCONFIG,
+ "modules": _T_MODULES,
+ "on": _T_ON,
+ "option": _T_OPTION,
+ "optional": _T_OPTIONAL,
+ "orsource": _T_ORSOURCE,
+ "osource": _T_OSOURCE,
+ "prompt": _T_PROMPT,
+ "range": _T_RANGE,
+ "rsource": _T_RSOURCE,
+ "select": _T_SELECT,
+ "source": _T_SOURCE,
+ "string": _T_STRING,
+ "tristate": _T_TRISTATE,
+ "visible": _T_VISIBLE,
}.get
# The constants below match the value of the corresponding tokens to remove the
# need for conversion
# Node types
-MENU = _T_MENU
+MENU = _T_MENU
COMMENT = _T_COMMENT
# Expression types
-AND = _T_AND
-OR = _T_OR
-NOT = _T_NOT
-EQUAL = _T_EQUAL
-UNEQUAL = _T_UNEQUAL
-LESS = _T_LESS
-LESS_EQUAL = _T_LESS_EQUAL
-GREATER = _T_GREATER
+AND = _T_AND
+OR = _T_OR
+NOT = _T_NOT
+EQUAL = _T_EQUAL
+UNEQUAL = _T_UNEQUAL
+LESS = _T_LESS
+LESS_EQUAL = _T_LESS_EQUAL
+GREATER = _T_GREATER
GREATER_EQUAL = _T_GREATER_EQUAL
REL_TO_STR = {
- EQUAL: "=",
- UNEQUAL: "!=",
- LESS: "<",
- LESS_EQUAL: "<=",
- GREATER: ">",
+ EQUAL: "=",
+ UNEQUAL: "!=",
+ LESS: "<",
+ LESS_EQUAL: "<=",
+ GREATER: ">",
GREATER_EQUAL: ">=",
}
# Symbol/choice types. UNKNOWN is 0 (falsy) to simplify some checks.
# Client code shouldn't rely on it though, as it was non-zero in
# older versions.
-UNKNOWN = 0
-BOOL = _T_BOOL
+UNKNOWN = 0
+BOOL = _T_BOOL
TRISTATE = _T_TRISTATE
-STRING = _T_STRING
-INT = _T_INT
-HEX = _T_HEX
+STRING = _T_STRING
+INT = _T_INT
+HEX = _T_HEX
TYPE_TO_STR = {
- UNKNOWN: "unknown",
- BOOL: "bool",
+ UNKNOWN: "unknown",
+ BOOL: "bool",
TRISTATE: "tristate",
- STRING: "string",
- INT: "int",
- HEX: "hex",
+ STRING: "string",
+ INT: "int",
+ HEX: "hex",
}
# Used in comparisons. 0 means the base is inferred from the format of the
# string.
_TYPE_TO_BASE = {
- HEX: 16,
- INT: 10,
- STRING: 0,
- UNKNOWN: 0,
+ HEX: 16,
+ INT: 10,
+ STRING: 0,
+ UNKNOWN: 0,
}
# def_bool -> BOOL, etc.
_DEF_TOKEN_TO_TYPE = {
- _T_DEF_BOOL: BOOL,
- _T_DEF_HEX: HEX,
- _T_DEF_INT: INT,
- _T_DEF_STRING: STRING,
+ _T_DEF_BOOL: BOOL,
+ _T_DEF_HEX: HEX,
+ _T_DEF_INT: INT,
+ _T_DEF_STRING: STRING,
_T_DEF_TRISTATE: TRISTATE,
}
@@ -7056,91 +7295,115 @@ _DEF_TOKEN_TO_TYPE = {
# Identifier-like lexemes ("missing quotes") are also treated as strings after
# these tokens. _T_CHOICE is included to avoid symbols being registered for
# named choices.
-_STRING_LEX = frozenset({
- _T_BOOL,
- _T_CHOICE,
- _T_COMMENT,
- _T_HEX,
- _T_INT,
- _T_MAINMENU,
- _T_MENU,
- _T_ORSOURCE,
- _T_OSOURCE,
- _T_PROMPT,
- _T_RSOURCE,
- _T_SOURCE,
- _T_STRING,
- _T_TRISTATE,
-})
+_STRING_LEX = frozenset(
+ {
+ _T_BOOL,
+ _T_CHOICE,
+ _T_COMMENT,
+ _T_HEX,
+ _T_INT,
+ _T_MAINMENU,
+ _T_MENU,
+ _T_ORSOURCE,
+ _T_OSOURCE,
+ _T_PROMPT,
+ _T_RSOURCE,
+ _T_SOURCE,
+ _T_STRING,
+ _T_TRISTATE,
+ }
+)
# Various sets for quick membership tests. Gives a single global lookup and
# avoids creating temporary dicts/tuples.
-_TYPE_TOKENS = frozenset({
- _T_BOOL,
- _T_TRISTATE,
- _T_INT,
- _T_HEX,
- _T_STRING,
-})
-
-_SOURCE_TOKENS = frozenset({
- _T_SOURCE,
- _T_RSOURCE,
- _T_OSOURCE,
- _T_ORSOURCE,
-})
-
-_REL_SOURCE_TOKENS = frozenset({
- _T_RSOURCE,
- _T_ORSOURCE,
-})
+_TYPE_TOKENS = frozenset(
+ {
+ _T_BOOL,
+ _T_TRISTATE,
+ _T_INT,
+ _T_HEX,
+ _T_STRING,
+ }
+)
+
+_SOURCE_TOKENS = frozenset(
+ {
+ _T_SOURCE,
+ _T_RSOURCE,
+ _T_OSOURCE,
+ _T_ORSOURCE,
+ }
+)
+
+_REL_SOURCE_TOKENS = frozenset(
+ {
+ _T_RSOURCE,
+ _T_ORSOURCE,
+ }
+)
# Obligatory (non-optional) sources
-_OBL_SOURCE_TOKENS = frozenset({
- _T_SOURCE,
- _T_RSOURCE,
-})
-
-_BOOL_TRISTATE = frozenset({
- BOOL,
- TRISTATE,
-})
-
-_BOOL_TRISTATE_UNKNOWN = frozenset({
- BOOL,
- TRISTATE,
- UNKNOWN,
-})
-
-_INT_HEX = frozenset({
- INT,
- HEX,
-})
-
-_SYMBOL_CHOICE = frozenset({
- Symbol,
- Choice,
-})
-
-_MENU_COMMENT = frozenset({
- MENU,
- COMMENT,
-})
-
-_EQUAL_UNEQUAL = frozenset({
- EQUAL,
- UNEQUAL,
-})
-
-_RELATIONS = frozenset({
- EQUAL,
- UNEQUAL,
- LESS,
- LESS_EQUAL,
- GREATER,
- GREATER_EQUAL,
-})
+_OBL_SOURCE_TOKENS = frozenset(
+ {
+ _T_SOURCE,
+ _T_RSOURCE,
+ }
+)
+
+_BOOL_TRISTATE = frozenset(
+ {
+ BOOL,
+ TRISTATE,
+ }
+)
+
+_BOOL_TRISTATE_UNKNOWN = frozenset(
+ {
+ BOOL,
+ TRISTATE,
+ UNKNOWN,
+ }
+)
+
+_INT_HEX = frozenset(
+ {
+ INT,
+ HEX,
+ }
+)
+
+_SYMBOL_CHOICE = frozenset(
+ {
+ Symbol,
+ Choice,
+ }
+)
+
+_MENU_COMMENT = frozenset(
+ {
+ MENU,
+ COMMENT,
+ }
+)
+
+_EQUAL_UNEQUAL = frozenset(
+ {
+ EQUAL,
+ UNEQUAL,
+ }
+)
+
+_RELATIONS = frozenset(
+ {
+ EQUAL,
+ UNEQUAL,
+ LESS,
+ LESS_EQUAL,
+ GREATER,
+ GREATER_EQUAL,
+ }
+)
# Helper functions for getting compiled regular expressions, with the needed
# matching function returned directly as a small optimization.
@@ -7189,7 +7452,7 @@ _string_special_search = _re_search(r'"|\'|\\|\$\(')
# Special characters/strings while expanding a symbol name. Also includes
# end-of-line, in case the macro is the last thing on the line.
-_name_special_search = _re_search(r'[^A-Za-z0-9_$/.-]|\$\(|$')
+_name_special_search = _re_search(r"[^A-Za-z0-9_$/.-]|\$\(|$")
# A valid right-hand side for an assignment to a string symbol in a .config
# file, including escaped characters. Extracts the contents.
diff --git a/util/run_ects.py b/util/run_ects.py
index 9178328e5f..9293f60779 100644
--- a/util/run_ects.py
+++ b/util/run_ects.py
@@ -16,81 +16,81 @@ import subprocess
import sys
# List of tests to run.
-TESTS = ['meta', 'gpio', 'hook', 'i2c', 'interrupt', 'mutex', 'task', 'timer']
+TESTS = ["meta", "gpio", "hook", "i2c", "interrupt", "mutex", "task", "timer"]
class CtsRunner(object):
- """Class running eCTS tests."""
-
- def __init__(self, ec_dir, dryrun):
- self.ec_dir = ec_dir
- self.cts_py = []
- if dryrun:
- self.cts_py += ['echo']
- self.cts_py += [os.path.join(ec_dir, 'cts/cts.py')]
-
- def run_cmd(self, cmd):
- try:
- rc = subprocess.call(cmd)
- if rc != 0:
- return False
- except OSError:
- return False
- return True
-
- def run_test(self, test):
- cmd = self.cts_py + ['-m', test]
- self.run_cmd(cmd)
-
- def run(self, tests):
- for test in tests:
- logging.info('Running', test, 'test.')
- self.run_test(test)
-
- def sync(self):
- logging.info('Syncing tree...')
- os.chdir(self.ec_dir)
- cmd = ['repo', 'sync', '.']
- return self.run_cmd(cmd)
-
- def upload(self):
- logging.info('Uploading results...')
+ """Class running eCTS tests."""
+
+ def __init__(self, ec_dir, dryrun):
+ self.ec_dir = ec_dir
+ self.cts_py = []
+ if dryrun:
+ self.cts_py += ["echo"]
+ self.cts_py += [os.path.join(ec_dir, "cts/cts.py")]
+
+ def run_cmd(self, cmd):
+ try:
+ rc = subprocess.call(cmd)
+ if rc != 0:
+ return False
+ except OSError:
+ return False
+ return True
+
+ def run_test(self, test):
+ cmd = self.cts_py + ["-m", test]
+ self.run_cmd(cmd)
+
+ def run(self, tests):
+ for test in tests:
+ logging.info("Running", test, "test.")
+ self.run_test(test)
+
+ def sync(self):
+ logging.info("Syncing tree...")
+ os.chdir(self.ec_dir)
+ cmd = ["repo", "sync", "."]
+ return self.run_cmd(cmd)
+
+ def upload(self):
+ logging.info("Uploading results...")
def main():
- if not os.path.exists('/etc/cros_chroot_version'):
- logging.error('This script has to run inside chroot.')
- sys.exit(-1)
-
- ec_dir = os.path.realpath(os.path.dirname(__file__) + '/..')
-
- parser = argparse.ArgumentParser(description='Run eCTS and report results.')
- parser.add_argument('-d',
- '--dryrun',
- action='store_true',
- help='Echo commands to be executed without running them.')
- parser.add_argument('-s',
- '--sync',
- action='store_true',
- help='Sync tree before running tests.')
- parser.add_argument('-u',
- '--upload',
- action='store_true',
- help='Upload test results.')
- args = parser.parse_args()
-
- runner = CtsRunner(ec_dir, args.dryrun)
-
- if args.sync:
- if not runner.sync():
- logging.error('Failed to sync.')
- sys.exit(-1)
-
- runner.run(TESTS)
-
- if args.upload:
- runner.upload()
-
-
-if __name__ == '__main__':
- main()
+ if not os.path.exists("/etc/cros_chroot_version"):
+ logging.error("This script has to run inside chroot.")
+ sys.exit(-1)
+
+ ec_dir = os.path.realpath(os.path.dirname(__file__) + "/..")
+
+ parser = argparse.ArgumentParser(description="Run eCTS and report results.")
+ parser.add_argument(
+ "-d",
+ "--dryrun",
+ action="store_true",
+ help="Echo commands to be executed without running them.",
+ )
+ parser.add_argument(
+ "-s", "--sync", action="store_true", help="Sync tree before running tests."
+ )
+ parser.add_argument(
+ "-u", "--upload", action="store_true", help="Upload test results."
+ )
+ args = parser.parse_args()
+
+ runner = CtsRunner(ec_dir, args.dryrun)
+
+ if args.sync:
+ if not runner.sync():
+ logging.error("Failed to sync.")
+ sys.exit(-1)
+
+ runner.run(TESTS)
+
+ if args.upload:
+ runner.upload()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/test_kconfig_check.py b/util/test_kconfig_check.py
index cd1b9bf098..db73e7ee71 100644
--- a/util/test_kconfig_check.py
+++ b/util/test_kconfig_check.py
@@ -16,7 +16,8 @@ import kconfig_check
# Prefix that we strip from each Kconfig option, when considering whether it is
# equivalent to a CONFIG option with the same name
-PREFIX = 'PLATFORM_EC_'
+PREFIX = "PLATFORM_EC_"
+
@contextlib.contextmanager
def capture_sys_output():
@@ -39,38 +40,49 @@ def capture_sys_output():
# directly from Python. You can still run this test with 'pytest' if you like.
class KconfigCheck(unittest.TestCase):
"""Tests for the KconfigCheck class"""
+
def test_simple_check(self):
"""Check it detected a new ad-hoc CONFIG"""
checker = kconfig_check.KconfigCheck()
- self.assertEqual(['NEW_ONE'], checker.find_new_adhoc(
- configs=['NEW_ONE', 'OLD_ONE', 'IN_KCONFIG'],
- kconfigs=['IN_KCONFIG'],
- allowed=['OLD_ONE']))
+ self.assertEqual(
+ ["NEW_ONE"],
+ checker.find_new_adhoc(
+ configs=["NEW_ONE", "OLD_ONE", "IN_KCONFIG"],
+ kconfigs=["IN_KCONFIG"],
+ allowed=["OLD_ONE"],
+ ),
+ )
def test_sorted_check(self):
"""Check it sorts the results in order"""
checker = kconfig_check.KconfigCheck()
self.assertSequenceEqual(
- ['ANOTHER_NEW_ONE', 'NEW_ONE'],
+ ["ANOTHER_NEW_ONE", "NEW_ONE"],
checker.find_new_adhoc(
- configs=['NEW_ONE', 'ANOTHER_NEW_ONE', 'OLD_ONE', 'IN_KCONFIG'],
- kconfigs=['IN_KCONFIG'],
- allowed=['OLD_ONE']))
+ configs=["NEW_ONE", "ANOTHER_NEW_ONE", "OLD_ONE", "IN_KCONFIG"],
+ kconfigs=["IN_KCONFIG"],
+ allowed=["OLD_ONE"],
+ ),
+ )
def check_read_configs(self, use_defines):
checker = kconfig_check.KconfigCheck()
with tempfile.NamedTemporaryFile() as configs:
- with open(configs.name, 'w') as out:
- prefix = '#define ' if use_defines else ''
- suffix = ' ' if use_defines else '='
- out.write(f'''{prefix}CONFIG_OLD_ONE{suffix}y
+ with open(configs.name, "w") as out:
+ prefix = "#define " if use_defines else ""
+ suffix = " " if use_defines else "="
+ out.write(
+ f"""{prefix}CONFIG_OLD_ONE{suffix}y
{prefix}NOT_A_CONFIG{suffix}
{prefix}CONFIG_STRING{suffix}"something"
{prefix}CONFIG_INT{suffix}123
{prefix}CONFIG_HEX{suffix}45ab
-''')
- self.assertEqual(['OLD_ONE', 'STRING', 'INT', 'HEX'],
- checker.read_configs(configs.name, use_defines))
+"""
+ )
+ self.assertEqual(
+ ["OLD_ONE", "STRING", "INT", "HEX"],
+ checker.read_configs(configs.name, use_defines),
+ )
def test_read_configs(self):
"""Test KconfigCheck.read_configs()"""
@@ -87,22 +99,24 @@ class KconfigCheck(unittest.TestCase):
Args:
srctree: Directory to write to
"""
- with open(os.path.join(srctree, 'Kconfig'), 'w') as out:
- out.write(f'''config {PREFIX}MY_KCONFIG
+ with open(os.path.join(srctree, "Kconfig"), "w") as out:
+ out.write(
+ f"""config {PREFIX}MY_KCONFIG
\tbool "my kconfig"
rsource "subdir/Kconfig.wibble"
-''')
- subdir = os.path.join(srctree, 'subdir')
+"""
+ )
+ subdir = os.path.join(srctree, "subdir")
os.mkdir(subdir)
- with open(os.path.join(subdir, 'Kconfig.wibble'), 'w') as out:
- out.write('menuconfig %sMENU_KCONFIG\n' % PREFIX)
+ with open(os.path.join(subdir, "Kconfig.wibble"), "w") as out:
+ out.write("menuconfig %sMENU_KCONFIG\n" % PREFIX)
# Add a directory which should be ignored
- bad_subdir = os.path.join(subdir, 'Kconfig')
+ bad_subdir = os.path.join(subdir, "Kconfig")
os.mkdir(bad_subdir)
- with open(os.path.join(bad_subdir, 'Kconfig.bad'), 'w') as out:
- out.write('menuconfig %sBAD_KCONFIG' % PREFIX)
+ with open(os.path.join(bad_subdir, "Kconfig.bad"), "w") as out:
+ out.write("menuconfig %sBAD_KCONFIG" % PREFIX)
def test_find_kconfigs(self):
"""Test KconfigCheck.find_kconfigs()"""
@@ -110,20 +124,20 @@ rsource "subdir/Kconfig.wibble"
with tempfile.TemporaryDirectory() as srctree:
self.setup_srctree(srctree)
files = checker.find_kconfigs(srctree)
- fnames = [fname[len(srctree):] for fname in files]
- self.assertEqual(['/Kconfig', '/subdir/Kconfig.wibble'], fnames)
+ fnames = [fname[len(srctree) :] for fname in files]
+ self.assertEqual(["/Kconfig", "/subdir/Kconfig.wibble"], fnames)
def test_scan_kconfigs(self):
"""Test KconfigCheck.scan_configs()"""
checker = kconfig_check.KconfigCheck()
with tempfile.TemporaryDirectory() as srctree:
self.setup_srctree(srctree)
- self.assertEqual(['MENU_KCONFIG', 'MY_KCONFIG'],
- checker.scan_kconfigs(srctree, PREFIX))
+ self.assertEqual(
+ ["MENU_KCONFIG", "MY_KCONFIG"], checker.scan_kconfigs(srctree, PREFIX)
+ )
@classmethod
- def setup_allowed_and_configs(cls, allowed_fname, configs_fname,
- add_new_one=True):
+ def setup_allowed_and_configs(cls, allowed_fname, configs_fname, add_new_one=True):
"""Set up the 'allowed' and 'configs' files for tests
Args:
@@ -131,14 +145,14 @@ rsource "subdir/Kconfig.wibble"
configs_fname: Filename to which CONFIGs to check should be written
add_new_one: True to add CONFIG_NEW_ONE to the configs_fname file
"""
- with open(allowed_fname, 'w') as out:
- out.write('CONFIG_OLD_ONE\n')
- out.write('CONFIG_MENU_KCONFIG\n')
- with open(configs_fname, 'w') as out:
- to_add = ['CONFIG_OLD_ONE', 'CONFIG_MY_KCONFIG']
+ with open(allowed_fname, "w") as out:
+ out.write("CONFIG_OLD_ONE\n")
+ out.write("CONFIG_MENU_KCONFIG\n")
+ with open(configs_fname, "w") as out:
+ to_add = ["CONFIG_OLD_ONE", "CONFIG_MY_KCONFIG"]
if add_new_one:
- to_add.append('CONFIG_NEW_ONE')
- out.write('\n'.join(to_add))
+ to_add.append("CONFIG_NEW_ONE")
+ out.write("\n".join(to_add))
def test_check_adhoc_configs(self):
"""Test KconfigCheck.check_adhoc_configs()"""
@@ -148,12 +162,16 @@ rsource "subdir/Kconfig.wibble"
with tempfile.NamedTemporaryFile() as allowed:
with tempfile.NamedTemporaryFile() as configs:
self.setup_allowed_and_configs(allowed.name, configs.name)
- new_adhoc, unneeded_adhoc, updated_adhoc = (
- checker.check_adhoc_configs(
- configs.name, srctree, allowed.name, PREFIX))
- self.assertEqual(['NEW_ONE'], new_adhoc)
- self.assertEqual(['MENU_KCONFIG'], unneeded_adhoc)
- self.assertEqual(['OLD_ONE'], updated_adhoc)
+ (
+ new_adhoc,
+ unneeded_adhoc,
+ updated_adhoc,
+ ) = checker.check_adhoc_configs(
+ configs.name, srctree, allowed.name, PREFIX
+ )
+ self.assertEqual(["NEW_ONE"], new_adhoc)
+ self.assertEqual(["MENU_KCONFIG"], unneeded_adhoc)
+ self.assertEqual(["OLD_ONE"], updated_adhoc)
def test_check(self):
"""Test running the 'check' subcommand"""
@@ -162,29 +180,39 @@ rsource "subdir/Kconfig.wibble"
self.setup_srctree(srctree)
with tempfile.NamedTemporaryFile() as allowed:
with tempfile.NamedTemporaryFile() as configs:
- self.setup_allowed_and_configs(allowed.name,
- configs.name)
+ self.setup_allowed_and_configs(allowed.name, configs.name)
ret_code = kconfig_check.main(
- ['-c', configs.name, '-s', srctree,
- '-a', allowed.name, '-p', PREFIX, 'check'])
+ [
+ "-c",
+ configs.name,
+ "-s",
+ srctree,
+ "-a",
+ allowed.name,
+ "-p",
+ PREFIX,
+ "check",
+ ]
+ )
self.assertEqual(1, ret_code)
- self.assertEqual('', stdout.getvalue())
- found = re.findall('(CONFIG_.*)', stderr.getvalue())
- self.assertEqual(['CONFIG_NEW_ONE'], found)
+ self.assertEqual("", stdout.getvalue())
+ found = re.findall("(CONFIG_.*)", stderr.getvalue())
+ self.assertEqual(["CONFIG_NEW_ONE"], found)
def test_real_kconfig(self):
"""Same Kconfig should be returned for kconfiglib / adhoc"""
if not kconfig_check.USE_KCONFIGLIB:
- self.skipTest('No kconfiglib available')
- zephyr_path = pathlib.Path('../../third_party/zephyr/main').resolve()
+ self.skipTest("No kconfiglib available")
+ zephyr_path = pathlib.Path("../../third_party/zephyr/main").resolve()
if not zephyr_path.exists():
- self.skipTest('No zephyr tree available')
+ self.skipTest("No zephyr tree available")
checker = kconfig_check.KconfigCheck()
- srcdir = 'zephyr'
+ srcdir = "zephyr"
search_paths = [zephyr_path]
kc_version = checker.scan_kconfigs(
- srcdir, search_paths=search_paths, try_kconfiglib=True)
+ srcdir, search_paths=search_paths, try_kconfiglib=True
+ )
adhoc_version = checker.scan_kconfigs(srcdir, try_kconfiglib=False)
# List of things missing from the Kconfig
@@ -192,15 +220,17 @@ rsource "subdir/Kconfig.wibble"
# The Kconfig is disjoint in some places, e.g. the boards have their
# own Kconfig files which are not included from the main Kconfig
- missing = [item for item in missing
- if not item.startswith('BOARD') and
- not item.startswith('VARIANT')]
+ missing = [
+ item
+ for item in missing
+ if not item.startswith("BOARD") and not item.startswith("VARIANT")
+ ]
# Similarly, some other items are defined in files that are not included
# in all cases, only for particular values of $(ARCH)
self.assertEqual(
- ['FLASH_LOAD_OFFSET', 'NPCX_HEADER', 'SYS_CLOCK_HW_CYCLES_PER_SEC'],
- missing)
+ ["FLASH_LOAD_OFFSET", "NPCX_HEADER", "SYS_CLOCK_HW_CYCLES_PER_SEC"], missing
+ )
def test_check_unneeded(self):
"""Test running the 'check' subcommand with unneeded ad-hoc configs"""
@@ -209,18 +239,29 @@ rsource "subdir/Kconfig.wibble"
self.setup_srctree(srctree)
with tempfile.NamedTemporaryFile() as allowed:
with tempfile.NamedTemporaryFile() as configs:
- self.setup_allowed_and_configs(allowed.name,
- configs.name, False)
+ self.setup_allowed_and_configs(
+ allowed.name, configs.name, False
+ )
ret_code = kconfig_check.main(
- ['-c', configs.name, '-s', srctree,
- '-a', allowed.name, '-p', PREFIX, 'check'])
+ [
+ "-c",
+ configs.name,
+ "-s",
+ srctree,
+ "-a",
+ allowed.name,
+ "-p",
+ PREFIX,
+ "check",
+ ]
+ )
self.assertEqual(1, ret_code)
- self.assertEqual('', stderr.getvalue())
- found = re.findall('(CONFIG_.*)', stdout.getvalue())
- self.assertEqual(['CONFIG_MENU_KCONFIG'], found)
+ self.assertEqual("", stderr.getvalue())
+ found = re.findall("(CONFIG_.*)", stdout.getvalue())
+ self.assertEqual(["CONFIG_MENU_KCONFIG"], found)
allowed = kconfig_check.NEW_ALLOWED_FNAME.read_text().splitlines()
- self.assertEqual(['CONFIG_OLD_ONE'], allowed)
+ self.assertEqual(["CONFIG_OLD_ONE"], allowed)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/util/uart_stress_tester.py b/util/uart_stress_tester.py
index b3db60060e..a89fe730c9 100755
--- a/util/uart_stress_tester.py
+++ b/util/uart_stress_tester.py
@@ -21,9 +21,7 @@ Prerequisite:
e.g. dut-control cr50_uart_timestamp:off
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
+from __future__ import absolute_import, division, print_function
import argparse
import atexit
@@ -36,472 +34,501 @@ import time
import serial
-BAUDRATE = 115200 # Default baudrate setting for UART port
-CROS_USERNAME = 'root' # Account name to login to ChromeOS
-CROS_PASSWORD = 'test0000' # Password to login to ChromeOS
-CHARGEN_TXT = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
- # The result of 'chargen 62 62'
+BAUDRATE = 115200 # Default baudrate setting for UART port
+CROS_USERNAME = "root" # Account name to login to ChromeOS
+CROS_PASSWORD = "test0000" # Password to login to ChromeOS
+CHARGEN_TXT = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+# The result of 'chargen 62 62'
CHARGEN_TXT_LEN = len(CHARGEN_TXT)
-CR = '\r' # Carriage Return
-LF = '\n' # Line Feed
+CR = "\r" # Carriage Return
+LF = "\n" # Line Feed
CRLF = CR + LF
-FLAG_FILENAME = '/tmp/chargen_testing'
-TPM_CMD = ('trunks_client --key_create --rsa=2048 --usage=sign'
- ' --key_blob=/tmp/blob &> /dev/null')
- # A ChromeOS TPM command for the cr50 stress
- # purpose.
-CR50_LOAD_GEN_CMD = ('while [[ -f %s ]]; do %s; done &'
- % (FLAG_FILENAME, TPM_CMD))
- # A command line to run TPM_CMD in background
- # infinitely.
+FLAG_FILENAME = "/tmp/chargen_testing"
+TPM_CMD = (
+ "trunks_client --key_create --rsa=2048 --usage=sign"
+ " --key_blob=/tmp/blob &> /dev/null"
+)
+# A ChromeOS TPM command for the cr50 stress
+# purpose.
+CR50_LOAD_GEN_CMD = "while [[ -f %s ]]; do %s; done &" % (FLAG_FILENAME, TPM_CMD)
+# A command line to run TPM_CMD in background
+# infinitely.
class ChargenTestError(Exception):
- """Exception for Uart Stress Test Error"""
- pass
+ """Exception for Uart Stress Test Error"""
+ pass
-class UartSerial(object):
- """Test Object for a single UART serial device
-
- Attributes:
- UART_DEV_PROFILES
- char_loss_occurrences: Number that character loss happens
- cleanup_cli: Command list to perform before the test exits
- cr50_workload: True if cr50 should be stressed, or False otherwise
- usb_output: True if output should be generated to USB channel
- dev_prof: Dictionary of device profile
- duration: Time to keep chargen running
- eol: Characters to add at the end of input
- logger: object that store the log
- num_ch_exp: Expected number of characters in output
- num_ch_cap: Number of captured characters in output
- test_cli: Command list to run for chargen test
- test_thread: Thread object that captures the UART output
- serial: serial.Serial object
- """
- UART_DEV_PROFILES = (
- # Kernel
- {
- 'prompt':'localhost login:',
- 'device_type':'AP',
- 'prepare_cmd':[
- CROS_USERNAME, # Login
- CROS_PASSWORD, # Password
- 'dmesg -D', # Disable console message
- 'touch ' + FLAG_FILENAME, # Create a temp file
- ],
- 'cleanup_cmd':[
- 'rm -f ' + FLAG_FILENAME, # Remove the temp file
- 'dmesg -E', # Enable console message
- 'logout', # Logout
- ],
- 'end_of_input':LF,
- },
- # EC
- {
- 'prompt':'> ',
- 'device_type':'EC',
- 'prepare_cmd':[
- 'chan save',
- 'chan 0' # Disable console message
- ],
- 'cleanup_cmd':['', 'chan restore'],
- 'end_of_input':CRLF,
- },
- )
-
- def __init__(self, port, duration, timeout=1,
- baudrate=BAUDRATE, cr50_workload=False,
- usb_output=False):
- """Initialize UartSerial
-
- Args:
- port: UART device path. e.g. /dev/ttyUSB0
- duration: Time to test, in seconds
- timeout: Read timeout value.
- baudrate: Baud rate such as 9600 or 115200.
- cr50_workload: True if a workload should be generated on cr50
- usb_output: True if a workload should be generated to USB channel
- """
-
- # Initialize serial object
- self.serial = serial.Serial()
- self.serial.port = port
- self.serial.timeout = timeout
- self.serial.baudrate = baudrate
-
- self.duration = duration
- self.cr50_workload = cr50_workload
- self.usb_output = usb_output
-
- self.logger = logging.getLogger(type(self).__name__ + '| ' + port)
- self.test_thread = threading.Thread(target=self.stress_test_thread)
- self.dev_prof = {}
- self.cleanup_cli = []
- self.test_cli = []
- self.eol = CRLF
- self.num_ch_exp = 0
- self.num_ch_cap = 0
- self.char_loss_occurrences = 0
- atexit.register(self.cleanup)
-
- def run_command(self, command_lines, delay=0):
- """Run command(s) at UART prompt
-
- Args:
- command_lines: list of commands to run.
- delay: delay after a command in second
+class UartSerial(object):
+ """Test Object for a single UART serial device
+
+ Attributes:
+ UART_DEV_PROFILES
+ char_loss_occurrences: Number that character loss happens
+ cleanup_cli: Command list to perform before the test exits
+ cr50_workload: True if cr50 should be stressed, or False otherwise
+ usb_output: True if output should be generated to USB channel
+ dev_prof: Dictionary of device profile
+ duration: Time to keep chargen running
+ eol: Characters to add at the end of input
+ logger: object that store the log
+ num_ch_exp: Expected number of characters in output
+ num_ch_cap: Number of captured characters in output
+ test_cli: Command list to run for chargen test
+ test_thread: Thread object that captures the UART output
+ serial: serial.Serial object
"""
- for cli in command_lines:
- self.logger.debug('run %r', cli)
-
- self.serial.write((cli + self.eol).encode())
- self.serial.flush()
- if delay:
- time.sleep(delay)
-
- def cleanup(self):
- """Before termination, clean up the UART device."""
- self.logger.debug('Closing...')
-
- self.serial.open()
- self.run_command(self.cleanup_cli) # Run cleanup commands
- self.serial.close()
- self.logger.debug('Cleanup done')
+ UART_DEV_PROFILES = (
+ # Kernel
+ {
+ "prompt": "localhost login:",
+ "device_type": "AP",
+ "prepare_cmd": [
+ CROS_USERNAME, # Login
+ CROS_PASSWORD, # Password
+ "dmesg -D", # Disable console message
+ "touch " + FLAG_FILENAME, # Create a temp file
+ ],
+ "cleanup_cmd": [
+ "rm -f " + FLAG_FILENAME, # Remove the temp file
+ "dmesg -E", # Enable console message
+ "logout", # Logout
+ ],
+ "end_of_input": LF,
+ },
+ # EC
+ {
+ "prompt": "> ",
+ "device_type": "EC",
+ "prepare_cmd": ["chan save", "chan 0"], # Disable console message
+ "cleanup_cmd": ["", "chan restore"],
+ "end_of_input": CRLF,
+ },
+ )
+
+ def __init__(
+ self,
+ port,
+ duration,
+ timeout=1,
+ baudrate=BAUDRATE,
+ cr50_workload=False,
+ usb_output=False,
+ ):
+ """Initialize UartSerial
+
+ Args:
+ port: UART device path. e.g. /dev/ttyUSB0
+ duration: Time to test, in seconds
+ timeout: Read timeout value.
+ baudrate: Baud rate such as 9600 or 115200.
+ cr50_workload: True if a workload should be generated on cr50
+ usb_output: True if a workload should be generated to USB channel
+ """
+
+ # Initialize serial object
+ self.serial = serial.Serial()
+ self.serial.port = port
+ self.serial.timeout = timeout
+ self.serial.baudrate = baudrate
+
+ self.duration = duration
+ self.cr50_workload = cr50_workload
+ self.usb_output = usb_output
+
+ self.logger = logging.getLogger(type(self).__name__ + "| " + port)
+ self.test_thread = threading.Thread(target=self.stress_test_thread)
+
+ self.dev_prof = {}
+ self.cleanup_cli = []
+ self.test_cli = []
+ self.eol = CRLF
+ self.num_ch_exp = 0
+ self.num_ch_cap = 0
+ self.char_loss_occurrences = 0
+ atexit.register(self.cleanup)
+
+ def run_command(self, command_lines, delay=0):
+ """Run command(s) at UART prompt
+
+ Args:
+ command_lines: list of commands to run.
+ delay: delay after a command in second
+ """
+ for cli in command_lines:
+ self.logger.debug("run %r", cli)
+
+ self.serial.write((cli + self.eol).encode())
+ self.serial.flush()
+ if delay:
+ time.sleep(delay)
+
+ def cleanup(self):
+ """Before termination, clean up the UART device."""
+ self.logger.debug("Closing...")
+
+ self.serial.open()
+ self.run_command(self.cleanup_cli) # Run cleanup commands
+ self.serial.close()
+
+ self.logger.debug("Cleanup done")
+
+ def get_output(self):
+ """Capture the UART output
+
+ Args:
+ stop_char: Read output buffer until it reads stop_char.
+
+ Returns:
+ text from UART output.
+ """
+ if self.serial.inWaiting() == 0:
+ time.sleep(1)
+
+ return self.serial.read(self.serial.inWaiting()).decode()
+
+ def prepare(self):
+ """Prepare the test:
+
+ Identify the type of UART device (EC or Kernel?), then
+ decide what kind of commands to use to generate stress loads.
+
+ Raises:
+ ChargenTestError if UART source can't be identified.
+ """
+ try:
+ self.logger.info("Preparing...")
+
+ self.serial.open()
+
+ # Prepare the device for test
+ self.serial.flushInput()
+ self.serial.flushOutput()
+
+ self.get_output() # drain data
+
+ # Give a couple of line feeds, and capture the prompt text
+ self.run_command(["", ""])
+ prompt_txt = self.get_output()
+
+ # Detect the device source: EC or AP?
+ # Detect if the device is AP or EC console based on the captured.
+ for dev_prof in self.UART_DEV_PROFILES:
+ if dev_prof["prompt"] in prompt_txt:
+ self.dev_prof = dev_prof
+ break
+ else:
+ # No prompt patterns were found. UART seems not responding or in
+ # an undesirable status.
+ if prompt_txt:
+ raise ChargenTestError(
+ "%s: Got an unknown prompt text: %s\n"
+ "Check manually whether %s is available."
+ % (self.serial.port, prompt_txt, self.serial.port)
+ )
+ else:
+ raise ChargenTestError(
+ "%s: Got no input. Close any other connections"
+ " to this port, and try it again." % self.serial.port
+ )
+
+ self.logger.info("Detected as %s UART", self.dev_prof["device_type"])
+ # Log displays the UART type (AP|EC) instead of device filename.
+ self.logger = logging.getLogger(
+ type(self).__name__ + "| " + self.dev_prof["device_type"]
+ )
+
+ # Either login to AP or run some commands to prepare the device
+ # for test
+ self.eol = self.dev_prof["end_of_input"]
+ self.run_command(self.dev_prof["prepare_cmd"], delay=2)
+ self.cleanup_cli += self.dev_prof["cleanup_cmd"]
+
+ # 'chargen' of AP does not have option for USB output.
+ # Force it work on UART.
+ if self.dev_prof["device_type"] == "AP":
+ self.usb_output = False
+
+ # Check whether the command 'chargen' is available in the device.
+ # 'chargen 1 4' is supposed to print '0000'
+ self.get_output() # drain data
+
+ chargen_cmd = "chargen 1 4"
+ if self.usb_output:
+ chargen_cmd += " usb"
+ self.run_command([chargen_cmd])
+ tmp_txt = self.get_output()
+
+ # Check whether chargen command is available.
+ if "0000" not in tmp_txt:
+ raise ChargenTestError(
+ "%s: Chargen got an unexpected result: %s"
+ % (self.dev_prof["device_type"], tmp_txt)
+ )
+
+ self.num_ch_exp = int(self.serial.baudrate * self.duration / 10)
+ chargen_cmd = "chargen " + str(CHARGEN_TXT_LEN) + " " + str(self.num_ch_exp)
+ if self.usb_output:
+ chargen_cmd += " usb"
+ self.test_cli = [chargen_cmd]
+
+ self.logger.info("Ready to test")
+ finally:
+ self.serial.close()
+
+ def stress_test_thread(self):
+ """Test thread
+
+ Raises:
+ ChargenTestError: if broken character is found.
+ """
+ try:
+ self.serial.open()
+ self.serial.flushInput()
+ self.serial.flushOutput()
+
+ # Run TPM command in background to burden cr50.
+ if self.dev_prof["device_type"] == "AP" and self.cr50_workload:
+ self.run_command([CR50_LOAD_GEN_CMD])
+ self.logger.debug("run TPM job while %s exists", FLAG_FILENAME)
+
+ # Run the command 'chargen', one time
+ self.run_command([""]) # Give a line feed
+ self.get_output() # Drain the output
+ self.run_command(self.test_cli)
+ self.serial.readline() # Drain the echoed command line.
+
+ err_msg = "%s: Expected %r but got %s after %d char received"
+
+ # Keep capturing the output until the test timer is expired.
+ self.num_ch_cap = 0
+ self.char_loss_occurrences = 0
+ data_starve_count = 0
+
+ total_num_ch = self.num_ch_exp # Expected number of characters in total
+ ch_exp = CHARGEN_TXT[0]
+ ch_cap = "z" # any character value is ok for loop initial condition.
+ while self.num_ch_cap < total_num_ch:
+ captured = self.get_output()
+
+ if captured:
+ # There is some output data. Reset the data starvation count.
+ data_starve_count = 0
+ else:
+ data_starve_count += 1
+ if data_starve_count > 1:
+ # If nothing was captured more than once, then terminate the test.
+ self.logger.debug("No more output")
+ break
+
+ for ch_cap in captured:
+ if ch_cap not in CHARGEN_TXT:
+ # If it is not alpha-numeric, terminate the test.
+ if ch_cap not in CRLF:
+ # If it is neither a CR nor LF, then it is an error case.
+ self.logger.error("Whole captured characters: %r", captured)
+ raise ChargenTestError(
+ err_msg
+ % (
+ "Broken char captured",
+ ch_exp,
+ hex(ord(ch_cap)),
+ self.num_ch_cap,
+ )
+ )
+
+ # Set the loop termination condition true.
+ total_num_ch = self.num_ch_cap
+
+ if self.num_ch_cap >= total_num_ch:
+ break
+
+ if ch_exp != ch_cap:
+ # If it is alpha-numeric but not continuous, then some characters
+ # are lost.
+ self.logger.error(
+ err_msg,
+ "Char loss detected",
+ ch_exp,
+ repr(ch_cap),
+ self.num_ch_cap,
+ )
+ self.char_loss_occurrences += 1
+
+ # Recalculate the expected number of characters to adjust
+ # termination condition. The loss might be bigger than this
+ # adjustment, but it is okay since it will terminates by either
+ # CR/LF detection or by data starvation.
+ idx_ch_exp = CHARGEN_TXT.find(ch_exp)
+ idx_ch_cap = CHARGEN_TXT.find(ch_cap)
+ if idx_ch_cap < idx_ch_exp:
+ idx_ch_cap += len(CHARGEN_TXT)
+ total_num_ch -= idx_ch_cap - idx_ch_exp
+
+ self.num_ch_cap += 1
+
+ # Determine What character is expected next?
+ ch_exp = CHARGEN_TXT[
+ (CHARGEN_TXT.find(ch_cap) + 1) % CHARGEN_TXT_LEN
+ ]
+
+ finally:
+ self.serial.close()
+
+ def start_test(self):
+ """Start the test thread"""
+ self.logger.info("Test thread starts")
+ self.test_thread.start()
+
+ def wait_test_done(self):
+ """Wait until the test thread get done and join"""
+ self.test_thread.join()
+ self.logger.info("Test thread is done")
+
+ def get_result(self):
+ """Display the result
+
+ Returns:
+ Integer = the number of lost character
+
+ Raises:
+ ChargenTestError: if the capture is corrupted.
+ """
+ # If more characters than expected are captured, it means some messages
+ # from other than chargen are mixed. Stop processing further.
+ if self.num_ch_exp < self.num_ch_cap:
+ raise ChargenTestError(
+ "%s: UART output is corrupted." % self.dev_prof["device_type"]
+ )
+
+ # Get the count difference between the expected to the captured
+ # as the number of lost character.
+ char_lost = self.num_ch_exp - self.num_ch_cap
+ self.logger.info(
+ "%8d char lost / %10d (%.1f %%)",
+ char_lost,
+ self.num_ch_exp,
+ char_lost * 100.0 / self.num_ch_exp,
+ )
+
+ return char_lost, self.num_ch_exp, self.char_loss_occurrences
- def get_output(self):
- """Capture the UART output
- Args:
- stop_char: Read output buffer until it reads stop_char.
+class ChargenTest(object):
+ """UART stress tester
- Returns:
- text from UART output.
+ Attributes:
+ logger: logging object
+ serials: Dictionary where key is filename of UART device, and the value is
+ UartSerial object
"""
- if self.serial.inWaiting() == 0:
- time.sleep(1)
-
- return self.serial.read(self.serial.inWaiting()).decode()
- def prepare(self):
- """Prepare the test:
-
- Identify the type of UART device (EC or Kernel?), then
- decide what kind of commands to use to generate stress loads.
-
- Raises:
- ChargenTestError if UART source can't be identified.
- """
- try:
- self.logger.info('Preparing...')
-
- self.serial.open()
-
- # Prepare the device for test
- self.serial.flushInput()
- self.serial.flushOutput()
-
- self.get_output() # drain data
-
- # Give a couple of line feeds, and capture the prompt text
- self.run_command(['', ''])
- prompt_txt = self.get_output()
-
- # Detect the device source: EC or AP?
- # Detect if the device is AP or EC console based on the captured.
- for dev_prof in self.UART_DEV_PROFILES:
- if dev_prof['prompt'] in prompt_txt:
- self.dev_prof = dev_prof
- break
- else:
- # No prompt patterns were found. UART seems not responding or in
- # an undesirable status.
- if prompt_txt:
- raise ChargenTestError('%s: Got an unknown prompt text: %s\n'
- 'Check manually whether %s is available.' %
- (self.serial.port, prompt_txt,
- self.serial.port))
- else:
- raise ChargenTestError('%s: Got no input. Close any other connections'
- ' to this port, and try it again.' %
- self.serial.port)
-
- self.logger.info('Detected as %s UART', self.dev_prof['device_type'])
- # Log displays the UART type (AP|EC) instead of device filename.
- self.logger = logging.getLogger(type(self).__name__ + '| ' +
- self.dev_prof['device_type'])
-
- # Either login to AP or run some commands to prepare the device
- # for test
- self.eol = self.dev_prof['end_of_input']
- self.run_command(self.dev_prof['prepare_cmd'], delay=2)
- self.cleanup_cli += self.dev_prof['cleanup_cmd']
-
- # 'chargen' of AP does not have option for USB output.
- # Force it work on UART.
- if self.dev_prof['device_type'] == 'AP':
- self.usb_output = False
-
- # Check whether the command 'chargen' is available in the device.
- # 'chargen 1 4' is supposed to print '0000'
- self.get_output() # drain data
-
- chargen_cmd = 'chargen 1 4'
- if self.usb_output:
- chargen_cmd += ' usb'
- self.run_command([chargen_cmd])
- tmp_txt = self.get_output()
-
- # Check whether chargen command is available.
- if '0000' not in tmp_txt:
- raise ChargenTestError('%s: Chargen got an unexpected result: %s' %
- (self.dev_prof['device_type'], tmp_txt))
-
- self.num_ch_exp = int(self.serial.baudrate * self.duration / 10)
- chargen_cmd = 'chargen ' + str(CHARGEN_TXT_LEN) + ' ' + \
- str(self.num_ch_exp)
- if self.usb_output:
- chargen_cmd += ' usb'
- self.test_cli = [chargen_cmd]
-
- self.logger.info('Ready to test')
- finally:
- self.serial.close()
-
- def stress_test_thread(self):
- """Test thread
-
- Raises:
- ChargenTestError: if broken character is found.
- """
- try:
- self.serial.open()
- self.serial.flushInput()
- self.serial.flushOutput()
-
- # Run TPM command in background to burden cr50.
- if self.dev_prof['device_type'] == 'AP' and self.cr50_workload:
- self.run_command([CR50_LOAD_GEN_CMD])
- self.logger.debug('run TPM job while %s exists', FLAG_FILENAME)
-
- # Run the command 'chargen', one time
- self.run_command(['']) # Give a line feed
- self.get_output() # Drain the output
- self.run_command(self.test_cli)
- self.serial.readline() # Drain the echoed command line.
-
- err_msg = '%s: Expected %r but got %s after %d char received'
-
- # Keep capturing the output until the test timer is expired.
- self.num_ch_cap = 0
- self.char_loss_occurrences = 0
- data_starve_count = 0
-
- total_num_ch = self.num_ch_exp # Expected number of characters in total
- ch_exp = CHARGEN_TXT[0]
- ch_cap = 'z' # any character value is ok for loop initial condition.
- while self.num_ch_cap < total_num_ch:
- captured = self.get_output()
-
- if captured:
- # There is some output data. Reset the data starvation count.
- data_starve_count = 0
+ def __init__(self, ports, duration, cr50_workload=False, usb_output=False):
+ """Initialize UART stress tester
+
+ Args:
+ ports: List of UART ports to test.
+ duration: Time to keep testing in seconds.
+ cr50_workload: True if a workload should be generated on cr50
+ usb_output: True if a workload should be generated to USB channel
+
+ Raises:
+ ChargenTestError: if any of ports is not a valid character device.
+ """
+
+ # Save the arguments
+ for port in ports:
+ try:
+ mode = os.stat(port).st_mode
+ except OSError as e:
+ raise ChargenTestError(e)
+ if not stat.S_ISCHR(mode):
+ raise ChargenTestError("%s is not a character device." % port)
+
+ if duration <= 0:
+ raise ChargenTestError("Input error: duration is not positive.")
+
+ # Initialize logging object
+ self.logger = logging.getLogger(type(self).__name__)
+
+ # Create an UartSerial object per UART port
+ self.serials = {} # UartSerial objects
+ for port in ports:
+ self.serials[port] = UartSerial(
+ port=port,
+ duration=duration,
+ cr50_workload=cr50_workload,
+ usb_output=usb_output,
+ )
+
+ def prepare(self):
+ """Prepare the test for each UART port"""
+ self.logger.info("Prepare ports for test")
+ for _, ser in self.serials.items():
+ ser.prepare()
+ self.logger.info("Ports are ready to test")
+
+ def print_result(self):
+ """Display the test result for each UART port
+
+ Returns:
+ char_lost: Total number of characters lost
+ """
+ char_lost = 0
+ for _, ser in self.serials.items():
+ (tmp_lost, _, _) = ser.get_result()
+ char_lost += tmp_lost
+
+ # If any characters are lost, then test fails.
+ msg = "lost %d character(s) from the test" % char_lost
+ if char_lost > 0:
+ self.logger.error("FAIL: %s", msg)
else:
- data_starve_count += 1
- if data_starve_count > 1:
- # If nothing was captured more than once, then terminate the test.
- self.logger.debug('No more output')
- break
-
- for ch_cap in captured:
- if ch_cap not in CHARGEN_TXT:
- # If it is not alpha-numeric, terminate the test.
- if ch_cap not in CRLF:
- # If it is neither a CR nor LF, then it is an error case.
- self.logger.error('Whole captured characters: %r', captured)
- raise ChargenTestError(err_msg % ('Broken char captured', ch_exp,
- hex(ord(ch_cap)),
- self.num_ch_cap))
-
- # Set the loop termination condition true.
- total_num_ch = self.num_ch_cap
-
- if self.num_ch_cap >= total_num_ch:
- break
-
- if ch_exp != ch_cap:
- # If it is alpha-numeric but not continuous, then some characters
- # are lost.
- self.logger.error(err_msg, 'Char loss detected',
- ch_exp, repr(ch_cap), self.num_ch_cap)
- self.char_loss_occurrences += 1
-
- # Recalculate the expected number of characters to adjust
- # termination condition. The loss might be bigger than this
- # adjustment, but it is okay since it will terminates by either
- # CR/LF detection or by data starvation.
- idx_ch_exp = CHARGEN_TXT.find(ch_exp)
- idx_ch_cap = CHARGEN_TXT.find(ch_cap)
- if idx_ch_cap < idx_ch_exp:
- idx_ch_cap += len(CHARGEN_TXT)
- total_num_ch -= (idx_ch_cap - idx_ch_exp)
-
- self.num_ch_cap += 1
-
- # Determine What character is expected next?
- ch_exp = CHARGEN_TXT[(CHARGEN_TXT.find(ch_cap) + 1) % CHARGEN_TXT_LEN]
-
- finally:
- self.serial.close()
-
- def start_test(self):
- """Start the test thread"""
- self.logger.info('Test thread starts')
- self.test_thread.start()
-
- def wait_test_done(self):
- """Wait until the test thread get done and join"""
- self.test_thread.join()
- self.logger.info('Test thread is done')
-
- def get_result(self):
- """Display the result
+ self.logger.info("PASS: %s", msg)
- Returns:
- Integer = the number of lost character
+ return char_lost
- Raises:
- ChargenTestError: if the capture is corrupted.
- """
- # If more characters than expected are captured, it means some messages
- # from other than chargen are mixed. Stop processing further.
- if self.num_ch_exp < self.num_ch_cap:
- raise ChargenTestError('%s: UART output is corrupted.' %
- self.dev_prof['device_type'])
+ def run(self):
+ """Run the stress test on UART port(s)
- # Get the count difference between the expected to the captured
- # as the number of lost character.
- char_lost = self.num_ch_exp - self.num_ch_cap
- self.logger.info('%8d char lost / %10d (%.1f %%)',
- char_lost, self.num_ch_exp,
- char_lost * 100.0 / self.num_ch_exp)
+ Raises:
+ ChargenTestError: If any characters are lost.
+ """
- return char_lost, self.num_ch_exp, self.char_loss_occurrences
+ # Detect UART source type, and decide which command to test.
+ self.prepare()
+ # Run the test on each UART port in thread.
+ self.logger.info("Test starts")
+ for _, ser in self.serials.items():
+ ser.start_test()
-class ChargenTest(object):
- """UART stress tester
+ # Wait all tests to finish.
+ for _, ser in self.serials.items():
+ ser.wait_test_done()
- Attributes:
- logger: logging object
- serials: Dictionary where key is filename of UART device, and the value is
- UartSerial object
- """
+ # Print the result.
+ char_lost = self.print_result()
+ if char_lost:
+ raise ChargenTestError("Test failed: lost %d character(s)" % char_lost)
- def __init__(self, ports, duration, cr50_workload=False,
- usb_output=False):
- """Initialize UART stress tester
+ self.logger.info("Test is done")
- Args:
- ports: List of UART ports to test.
- duration: Time to keep testing in seconds.
- cr50_workload: True if a workload should be generated on cr50
- usb_output: True if a workload should be generated to USB channel
- Raises:
- ChargenTestError: if any of ports is not a valid character device.
- """
+def parse_args(cmdline):
+ """Parse command line arguments.
- # Save the arguments
- for port in ports:
- try:
- mode = os.stat(port).st_mode
- except OSError as e:
- raise ChargenTestError(e)
- if not stat.S_ISCHR(mode):
- raise ChargenTestError('%s is not a character device.' % port)
-
- if duration <= 0:
- raise ChargenTestError('Input error: duration is not positive.')
-
- # Initialize logging object
- self.logger = logging.getLogger(type(self).__name__)
-
- # Create an UartSerial object per UART port
- self.serials = {} # UartSerial objects
- for port in ports:
- self.serials[port] = UartSerial(port=port, duration=duration,
- cr50_workload=cr50_workload,
- usb_output=usb_output)
-
- def prepare(self):
- """Prepare the test for each UART port"""
- self.logger.info('Prepare ports for test')
- for _, ser in self.serials.items():
- ser.prepare()
- self.logger.info('Ports are ready to test')
-
- def print_result(self):
- """Display the test result for each UART port
+ Args:
+ cmdline: list to be parsed
Returns:
- char_lost: Total number of characters lost
- """
- char_lost = 0
- for _, ser in self.serials.items():
- (tmp_lost, _, _) = ser.get_result()
- char_lost += tmp_lost
-
- # If any characters are lost, then test fails.
- msg = 'lost %d character(s) from the test' % char_lost
- if char_lost > 0:
- self.logger.error('FAIL: %s', msg)
- else:
- self.logger.info('PASS: %s', msg)
-
- return char_lost
-
- def run(self):
- """Run the stress test on UART port(s)
-
- Raises:
- ChargenTestError: If any characters are lost.
+ tuple (options, args) where args is a list of cmdline arguments that the
+ parser was unable to match i.e. they're servod controls, not options.
"""
-
- # Detect UART source type, and decide which command to test.
- self.prepare()
-
- # Run the test on each UART port in thread.
- self.logger.info('Test starts')
- for _, ser in self.serials.items():
- ser.start_test()
-
- # Wait all tests to finish.
- for _, ser in self.serials.items():
- ser.wait_test_done()
-
- # Print the result.
- char_lost = self.print_result()
- if char_lost:
- raise ChargenTestError('Test failed: lost %d character(s)' %
- char_lost)
-
- self.logger.info('Test is done')
-
-def parse_args(cmdline):
- """Parse command line arguments.
-
- Args:
- cmdline: list to be parsed
-
- Returns:
- tuple (options, args) where args is a list of cmdline arguments that the
- parser was unable to match i.e. they're servod controls, not options.
- """
- description = """%(prog)s repeats sending a uart console command
+ description = """%(prog)s repeats sending a uart console command
to each UART device for a given time, and check if output
has any missing characters.
@@ -511,52 +538,70 @@ Examples:
%(prog)s /dev/ttyUSB1 /dev/ttyUSB2 --cr50
"""
- parser = argparse.ArgumentParser(description=description,
- formatter_class=argparse.RawTextHelpFormatter
- )
- parser.add_argument('port', type=str, nargs='*',
- help='UART device path to test')
- parser.add_argument('-c', '--cr50', action='store_true', default=False,
- help='generate TPM workload on cr50')
- parser.add_argument('-d', '--debug', action='store_true', default=False,
- help='enable debug messages')
- parser.add_argument('-t', '--time', type=int,
- help='Test duration in second', default=300)
- parser.add_argument('-u', '--usb', action='store_true', default=False,
- help='Generate output to USB channel instead')
- return parser.parse_known_args(cmdline)
+ parser = argparse.ArgumentParser(
+ description=description, formatter_class=argparse.RawTextHelpFormatter
+ )
+ parser.add_argument("port", type=str, nargs="*", help="UART device path to test")
+ parser.add_argument(
+ "-c",
+ "--cr50",
+ action="store_true",
+ default=False,
+ help="generate TPM workload on cr50",
+ )
+ parser.add_argument(
+ "-d",
+ "--debug",
+ action="store_true",
+ default=False,
+ help="enable debug messages",
+ )
+ parser.add_argument(
+ "-t", "--time", type=int, help="Test duration in second", default=300
+ )
+ parser.add_argument(
+ "-u",
+ "--usb",
+ action="store_true",
+ default=False,
+ help="Generate output to USB channel instead",
+ )
+ return parser.parse_known_args(cmdline)
def main():
- """Main function wrapper"""
- try:
- (options, _) = parse_args(sys.argv[1:])
-
- # Set Log format
- log_format = '%(asctime)s %(levelname)-6s | %(name)-25s'
- date_format = '%Y-%m-%d %H:%M:%S'
- if options.debug:
- log_format += ' | %(filename)s:%(lineno)4d:%(funcName)-18s'
- loglevel = logging.DEBUG
- else:
- loglevel = logging.INFO
- log_format += ' | %(message)s'
-
- logging.basicConfig(level=loglevel, format=log_format,
- datefmt=date_format)
-
- # Create a ChargenTest object
- utest = ChargenTest(options.port, options.time,
- cr50_workload=options.cr50,
- usb_output=options.usb)
- utest.run() # Run
-
- except KeyboardInterrupt:
- sys.exit(0)
-
- except ChargenTestError as e:
- logging.error(str(e))
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
+ """Main function wrapper"""
+ try:
+ (options, _) = parse_args(sys.argv[1:])
+
+ # Set Log format
+ log_format = "%(asctime)s %(levelname)-6s | %(name)-25s"
+ date_format = "%Y-%m-%d %H:%M:%S"
+ if options.debug:
+ log_format += " | %(filename)s:%(lineno)4d:%(funcName)-18s"
+ loglevel = logging.DEBUG
+ else:
+ loglevel = logging.INFO
+ log_format += " | %(message)s"
+
+ logging.basicConfig(level=loglevel, format=log_format, datefmt=date_format)
+
+ # Create a ChargenTest object
+ utest = ChargenTest(
+ options.port,
+ options.time,
+ cr50_workload=options.cr50,
+ usb_output=options.usb,
+ )
+ utest.run() # Run
+
+ except KeyboardInterrupt:
+ sys.exit(0)
+
+ except ChargenTestError as e:
+ logging.error(str(e))
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/unpack_ftb.py b/util/unpack_ftb.py
index 03127a7089..a68662d82b 100755
--- a/util/unpack_ftb.py
+++ b/util/unpack_ftb.py
@@ -10,26 +10,28 @@
# Note: This is a py2/3 compatible file.
from __future__ import print_function
+
import argparse
import ctypes
import os
class Header(ctypes.Structure):
- _pack_ = 1
- _fields_ = [
- ('signature', ctypes.c_uint32),
- ('ftb_ver', ctypes.c_uint32),
- ('chip_id', ctypes.c_uint32),
- ('svn_ver', ctypes.c_uint32),
- ('fw_ver', ctypes.c_uint32),
- ('config_id', ctypes.c_uint32),
- ('config_ver', ctypes.c_uint32),
- ('reserved', ctypes.c_uint8 * 8),
- ('release_info', ctypes.c_ulonglong),
- ('sec_size', ctypes.c_uint32 * 4),
- ('crc', ctypes.c_uint32),
- ]
+ _pack_ = 1
+ _fields_ = [
+ ("signature", ctypes.c_uint32),
+ ("ftb_ver", ctypes.c_uint32),
+ ("chip_id", ctypes.c_uint32),
+ ("svn_ver", ctypes.c_uint32),
+ ("fw_ver", ctypes.c_uint32),
+ ("config_id", ctypes.c_uint32),
+ ("config_ver", ctypes.c_uint32),
+ ("reserved", ctypes.c_uint8 * 8),
+ ("release_info", ctypes.c_ulonglong),
+ ("sec_size", ctypes.c_uint32 * 4),
+ ("crc", ctypes.c_uint32),
+ ]
+
FW_HEADER_SIZE = 64
FW_HEADER_SIGNATURE = 0xAA55AA55
@@ -44,7 +46,7 @@ FLASH_SEC_ADDR = [
0x0000 * 4, # CODE
0x7C00 * 4, # CONFIG
0x7000 * 4, # CX
- None # This section shouldn't exist
+ None, # This section shouldn't exist
]
UPDATE_PDU_SIZE = 4096
@@ -59,64 +61,66 @@ OUTPUT_FILE_SIZE = UPDATE_PDU_SIZE + 128 * 1024
def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--input', '-i', required=True)
- parser.add_argument('--output', '-o', required=True)
- args = parser.parse_args()
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--input", "-i", required=True)
+ parser.add_argument("--output", "-o", required=True)
+ args = parser.parse_args()
- with open(args.input, 'rb') as f:
- bs = f.read()
+ with open(args.input, "rb") as f:
+ bs = f.read()
- size = len(bs)
- if size < FW_HEADER_SIZE + FW_BYTES_ALIGN:
- raise Exception('FW size too small')
+ size = len(bs)
+ if size < FW_HEADER_SIZE + FW_BYTES_ALIGN:
+ raise Exception("FW size too small")
- print('FTB file size:', size)
+ print("FTB file size:", size)
- header = Header()
- assert ctypes.sizeof(header) == FW_HEADER_SIZE
+ header = Header()
+ assert ctypes.sizeof(header) == FW_HEADER_SIZE
- ctypes.memmove(ctypes.addressof(header), bs, ctypes.sizeof(header))
- if (header.signature != FW_HEADER_SIGNATURE or
- header.ftb_ver != FW_FTB_VER or
- header.chip_id != FW_CHIP_ID):
- raise Exception('Invalid header')
+ ctypes.memmove(ctypes.addressof(header), bs, ctypes.sizeof(header))
+ if (
+ header.signature != FW_HEADER_SIGNATURE
+ or header.ftb_ver != FW_FTB_VER
+ or header.chip_id != FW_CHIP_ID
+ ):
+ raise Exception("Invalid header")
- for key, _ in header._fields_:
- v = getattr(header, key)
- if isinstance(v, ctypes.Array):
- print(key, list(map(hex, v)))
- else:
- print(key, hex(v))
+ for key, _ in header._fields_:
+ v = getattr(header, key)
+ if isinstance(v, ctypes.Array):
+ print(key, list(map(hex, v)))
+ else:
+ print(key, hex(v))
- dimension = sum(header.sec_size)
+ dimension = sum(header.sec_size)
- assert dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN == size
- data = bs[FW_HEADER_SIZE:FW_HEADER_SIZE + dimension]
+ assert dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN == size
+ data = bs[FW_HEADER_SIZE : FW_HEADER_SIZE + dimension]
- with open(args.output, 'wb') as f:
- # ensure the file size
- f.seek(OUTPUT_FILE_SIZE - 1, os.SEEK_SET)
- f.write(b'\x00')
+ with open(args.output, "wb") as f:
+ # ensure the file size
+ f.seek(OUTPUT_FILE_SIZE - 1, os.SEEK_SET)
+ f.write(b"\x00")
- f.seek(0, os.SEEK_SET)
- f.write(bs[0 : ctypes.sizeof(header)])
+ f.seek(0, os.SEEK_SET)
+ f.write(bs[0 : ctypes.sizeof(header)])
- offset = 0
- # write each sections
- for i, addr in enumerate(FLASH_SEC_ADDR):
- size = header.sec_size[i]
- assert addr is not None or size == 0
+ offset = 0
+ # write each sections
+ for i, addr in enumerate(FLASH_SEC_ADDR):
+ size = header.sec_size[i]
+ assert addr is not None or size == 0
- if size == 0:
- continue
+ if size == 0:
+ continue
- f.seek(UPDATE_PDU_SIZE + addr, os.SEEK_SET)
- f.write(data[offset : offset + size])
- offset += size
+ f.seek(UPDATE_PDU_SIZE + addr, os.SEEK_SET)
+ f.write(data[offset : offset + size])
+ offset += size
- f.flush()
+ f.flush()
-if __name__ == '__main__':
- main()
+if __name__ == "__main__":
+ main()
diff --git a/util/update_release_branch.py b/util/update_release_branch.py
index b9063d4970..4d9c89df4a 100755
--- a/util/update_release_branch.py
+++ b/util/update_release_branch.py
@@ -19,8 +19,7 @@ import subprocess
import sys
import textwrap
-
-BUG_NONE_PATTERN = re.compile('none', flags=re.IGNORECASE)
+BUG_NONE_PATTERN = re.compile("none", flags=re.IGNORECASE)
def git_commit_msg(branch, head, merge_head, rel_paths, cmd):
@@ -42,18 +41,17 @@ def git_commit_msg(branch, head, merge_head, rel_paths, cmd):
A String containing the git commit message with the exception of the
Signed-Off-By field and Change-ID field.
"""
- relevant_commits_cmd, relevant_commits = get_relevant_commits(head,
- merge_head,
- '--oneline',
- rel_paths)
+ relevant_commits_cmd, relevant_commits = get_relevant_commits(
+ head, merge_head, "--oneline", rel_paths
+ )
- _, relevant_bugs = get_relevant_commits(head, merge_head, '', rel_paths)
- relevant_bugs = set(re.findall('BUG=(.*)', relevant_bugs))
+ _, relevant_bugs = get_relevant_commits(head, merge_head, "", rel_paths)
+ relevant_bugs = set(re.findall("BUG=(.*)", relevant_bugs))
# Filter out "none" from set of bugs
filtered = []
for bug_line in relevant_bugs:
- bug_line = bug_line.replace(',', ' ')
- bugs = bug_line.split(' ')
+ bug_line = bug_line.replace(",", " ")
+ bugs = bug_line.split(" ")
for bug in bugs:
if bug and not BUG_NONE_PATTERN.match(bug):
filtered.append(bug)
@@ -82,18 +80,20 @@ Cq-Include-Trybots: chromeos/cq:cq-orchestrator
# 72 cols.
relevant_commits_cmd = textwrap.fill(relevant_commits_cmd, width=72)
# Wrap at 68 cols to save room for 'BUG='
- bugs = textwrap.wrap(' '.join(relevant_bugs), width=68)
- bug_field = ''
+ bugs = textwrap.wrap(" ".join(relevant_bugs), width=68)
+ bug_field = ""
for line in bugs:
- bug_field += 'BUG=' + line + '\n'
+ bug_field += "BUG=" + line + "\n"
# Remove the final newline since the template adds it for us.
bug_field = bug_field[:-1]
- return COMMIT_MSG_TEMPLATE.format(BRANCH=branch,
- RELEVANT_COMMITS_CMD=relevant_commits_cmd,
- RELEVANT_COMMITS=relevant_commits,
- BUG_FIELD=bug_field,
- COMMAND_LINE=cmd)
+ return COMMIT_MSG_TEMPLATE.format(
+ BRANCH=branch,
+ RELEVANT_COMMITS_CMD=relevant_commits_cmd,
+ RELEVANT_COMMITS=relevant_commits,
+ BUG_FIELD=bug_field,
+ COMMAND_LINE=cmd,
+ )
def get_relevant_boards(baseboard):
@@ -105,15 +105,16 @@ def get_relevant_boards(baseboard):
Returns:
A list of strings containing the boards based off of the baseboard.
"""
- proc = subprocess.run(['git', 'grep', 'BASEBOARD:=' + baseboard, '--',
- 'board/'],
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True)
+ proc = subprocess.run(
+ ["git", "grep", "BASEBOARD:=" + baseboard, "--", "board/"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ check=True,
+ )
boards = []
res = proc.stdout.splitlines()
for line in res:
- boards.append(line.split('/')[1])
+ boards.append(line.split("/")[1])
return boards
@@ -135,21 +136,18 @@ def get_relevant_commits(head, merge_head, fmt, relevant_paths):
stdout.
"""
if fmt:
- cmd = ['git', 'log', fmt, head + '..' + merge_head, '--',
- relevant_paths]
+ cmd = ["git", "log", fmt, head + ".." + merge_head, "--", relevant_paths]
else:
- cmd = ['git', 'log', head + '..' + merge_head, '--', relevant_paths]
+ cmd = ["git", "log", head + ".." + merge_head, "--", relevant_paths]
# Pass cmd as a string to subprocess.run() since we need to run with shell
# equal to True. The reason we are using shell equal to True is to take
# advantage of the glob expansion for the relevant paths.
- cmd = ' '.join(cmd)
- proc = subprocess.run(cmd,
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True,
- shell=True)
- return ''.join(proc.args), proc.stdout
+ cmd = " ".join(cmd)
+ proc = subprocess.run(
+ cmd, stdout=subprocess.PIPE, encoding="utf-8", check=True, shell=True
+ )
+ return "".join(proc.args), proc.stdout
def main(argv):
@@ -165,46 +163,61 @@ def main(argv):
argv: A list of the command line arguments passed to this script.
"""
# Set up argument parser.
- parser = argparse.ArgumentParser(description=('A script that generates a '
- 'merge commit from cros/main'
- ' to a desired release '
- 'branch. By default, the '
- '"recursive" merge strategy '
- 'with the "theirs" strategy '
- 'option is used.'))
- parser.add_argument('--baseboard')
- parser.add_argument('--board')
- parser.add_argument('release_branch', help=('The name of the target release'
- ' branch'))
- parser.add_argument('--relevant_paths_file',
- help=('A path to a text file which includes other '
- 'relevant paths of interest for this board '
- 'or baseboard'))
- parser.add_argument('--merge_strategy', '-s', default='recursive',
- help='The merge strategy to pass to `git merge -s`')
- parser.add_argument('--strategy_option', '-X',
- help=('The strategy option for the chosen merge '
- 'strategy'))
+ parser = argparse.ArgumentParser(
+ description=(
+ "A script that generates a "
+ "merge commit from cros/main"
+ " to a desired release "
+ "branch. By default, the "
+ '"recursive" merge strategy '
+ 'with the "theirs" strategy '
+ "option is used."
+ )
+ )
+ parser.add_argument("--baseboard")
+ parser.add_argument("--board")
+ parser.add_argument(
+ "release_branch", help=("The name of the target release" " branch")
+ )
+ parser.add_argument(
+ "--relevant_paths_file",
+ help=(
+ "A path to a text file which includes other "
+ "relevant paths of interest for this board "
+ "or baseboard"
+ ),
+ )
+ parser.add_argument(
+ "--merge_strategy",
+ "-s",
+ default="recursive",
+ help="The merge strategy to pass to `git merge -s`",
+ )
+ parser.add_argument(
+ "--strategy_option",
+ "-X",
+ help=("The strategy option for the chosen merge " "strategy"),
+ )
opts = parser.parse_args(argv[1:])
- baseboard_dir = ''
- board_dir = ''
+ baseboard_dir = ""
+ board_dir = ""
if opts.baseboard:
# Dereference symlinks so "git log" works as expected.
- baseboard_dir = os.path.relpath('baseboard/' + opts.baseboard)
+ baseboard_dir = os.path.relpath("baseboard/" + opts.baseboard)
baseboard_dir = os.path.relpath(os.path.realpath(baseboard_dir))
boards = get_relevant_boards(opts.baseboard)
elif opts.board:
- board_dir = os.path.relpath('board/' + opts.board)
+ board_dir = os.path.relpath("board/" + opts.board)
board_dir = os.path.relpath(os.path.realpath(board_dir))
boards = [opts.board]
else:
- parser.error('You must specify a board OR a baseboard')
+ parser.error("You must specify a board OR a baseboard")
- print('Gathering relevant paths...')
+ print("Gathering relevant paths...")
relevant_paths = []
if opts.baseboard:
relevant_paths.append(baseboard_dir)
@@ -212,65 +225,91 @@ def main(argv):
relevant_paths.append(board_dir)
for board in boards:
- relevant_paths.append('board/' + board)
+ relevant_paths.append("board/" + board)
# Check for the existence of a file that has other paths of interest.
if opts.relevant_paths_file and os.path.exists(opts.relevant_paths_file):
- with open(opts.relevant_paths_file, 'r') as relevant_paths_file:
+ with open(opts.relevant_paths_file, "r") as relevant_paths_file:
for line in relevant_paths_file:
- if not line.startswith('#'):
+ if not line.startswith("#"):
relevant_paths.append(line.rstrip())
- relevant_paths.append('util/getversion.sh')
- relevant_paths = ' '.join(relevant_paths)
+ relevant_paths.append("util/getversion.sh")
+ relevant_paths = " ".join(relevant_paths)
# Check if we are already in merge process
- result = subprocess.run(['git', 'rev-parse', '--quiet', '--verify',
- 'MERGE_HEAD'], stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL, check=False)
+ result = subprocess.run(
+ ["git", "rev-parse", "--quiet", "--verify", "MERGE_HEAD"],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ check=False,
+ )
if result.returncode:
# Let's perform the merge
- print('Updating remote...')
- subprocess.run(['git', 'remote', 'update'], check=True)
- subprocess.run(['git', 'checkout', '-B', opts.release_branch, 'cros/' +
- opts.release_branch], check=True)
- print('Attempting git merge...')
- if opts.merge_strategy == 'recursive' and not opts.strategy_option:
- opts.strategy_option = 'theirs'
- print('Using "%s" merge strategy' % opts.merge_strategy,
- ("with strategy option '%s'" % opts.strategy_option
- if opts.strategy_option else ''))
- arglist = ['git', 'merge', '--no-ff', '--no-commit', 'cros/main', '-s',
- opts.merge_strategy]
+ print("Updating remote...")
+ subprocess.run(["git", "remote", "update"], check=True)
+ subprocess.run(
+ [
+ "git",
+ "checkout",
+ "-B",
+ opts.release_branch,
+ "cros/" + opts.release_branch,
+ ],
+ check=True,
+ )
+ print("Attempting git merge...")
+ if opts.merge_strategy == "recursive" and not opts.strategy_option:
+ opts.strategy_option = "theirs"
+ print(
+ 'Using "%s" merge strategy' % opts.merge_strategy,
+ (
+ "with strategy option '%s'" % opts.strategy_option
+ if opts.strategy_option
+ else ""
+ ),
+ )
+ arglist = [
+ "git",
+ "merge",
+ "--no-ff",
+ "--no-commit",
+ "cros/main",
+ "-s",
+ opts.merge_strategy,
+ ]
if opts.strategy_option:
- arglist.append('-X' + opts.strategy_option)
+ arglist.append("-X" + opts.strategy_option)
subprocess.run(arglist, check=True)
else:
- print('We have already started merge process.',
- 'Attempt to generate commit.')
-
- print('Generating commit message...')
- branch = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True).stdout.rstrip()
- head = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'],
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True).stdout.rstrip()
- merge_head = subprocess.run(['git', 'rev-parse', '--short',
- 'MERGE_HEAD'],
- stdout=subprocess.PIPE,
- encoding='utf-8',
- check=True).stdout.rstrip()
-
- cmd = ' '.join(argv)
- print('Typing as fast as I can...')
+ print("We have already started merge process.", "Attempt to generate commit.")
+
+ print("Generating commit message...")
+ branch = subprocess.run(
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ check=True,
+ ).stdout.rstrip()
+ head = subprocess.run(
+ ["git", "rev-parse", "--short", "HEAD"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ check=True,
+ ).stdout.rstrip()
+ merge_head = subprocess.run(
+ ["git", "rev-parse", "--short", "MERGE_HEAD"],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ check=True,
+ ).stdout.rstrip()
+
+ cmd = " ".join(argv)
+ print("Typing as fast as I can...")
commit_msg = git_commit_msg(branch, head, merge_head, relevant_paths, cmd)
- subprocess.run(['git', 'commit', '--signoff', '-m', commit_msg], check=True)
- subprocess.run(['git', 'commit', '--amend'], check=True)
- print(("Finished! **Please review the commit to see if it's to your "
- 'liking.**'))
+ subprocess.run(["git", "commit", "--signoff", "-m", commit_msg], check=True)
+ subprocess.run(["git", "commit", "--amend"], check=True)
+ print(("Finished! **Please review the commit to see if it's to your " "liking.**"))
-if __name__ == '__main__':
+if __name__ == "__main__":
main(sys.argv)