Update littlecheck

This allows:

- Running scripts via shebang (not important here)
- Progress output (so we can ditch more of our run script)
- Context (only after, for now) - this is important if there is a test failure
This commit is contained in:
Fabian Homborg 2020-01-25 11:37:10 +01:00
parent d910bada82
commit 25810b70f2

View File

@ -27,6 +27,10 @@ class Config(object):
self.verbose = False
# Whether output gets ANSI colorization.
self.colorize = False
# Whether to show which file was tested.
self.progress = False
# How many after lines to print
self.after = 5
def colors(self):
""" Return a dictionary mapping color names to ANSI escapes """
@ -112,13 +116,16 @@ class RunCmd(object):
class TestFailure(object):
def __init__(self, line, check, testrun):
def __init__(self, line, check, testrun, after = None):
self.line = line
self.check = check
self.testrun = testrun
self.error_annotation_line = None
# The output that comes *after* the failure.
self.after = after
def message(self):
afterlines = self.testrun.config.after
fields = self.testrun.config.colors()
fields["name"] = self.testrun.name
fields["subbed_command"] = self.testrun.subbed_command
@ -139,7 +146,8 @@ class TestFailure(object):
"check_type": self.check.type,
}
)
fmtstrs = ["{RED}Failure{RESET} in {name}:", ""]
filemsg = "" if self.testrun.config.progress else " in {name}"
fmtstrs = ["{RED}Failure{RESET}" + filemsg + ":", ""]
if self.line and self.check:
fmtstrs += [
" The {check_type} on line {input_lineno} wants:",
@ -171,6 +179,12 @@ class TestFailure(object):
" additional output on stderr:{error_annotation_lineno}:",
" {BOLD}{error_annotation}{RESET}",
]
if self.after:
fields["additional_output"] = " ".join(self.after[:afterlines])
fmtstrs += [
" additional output:",
" {BOLD}{additional_output}{RESET}",
]
fmtstrs += [" when running command:", " {subbed_command}"]
return "\n".join(fmtstrs).format(**fields)
@ -194,7 +208,9 @@ def perform_substitution(input_str, subs):
for key, replacement in subs_ordered:
if text.startswith(key):
return replacement + text[len(key) :]
raise CheckerError("Unknown substitution: " + m.group(0))
# No substitution found, so we default to running it as-is,
# which will end up running it via $PATH.
return text
return re.sub(r"%(%|[a-zA-Z0-9_-]+)", subber, input_str)
@ -224,7 +240,9 @@ class TestRun(object):
lineq.pop()
else:
# Failed to match.
return TestFailure(line, check, self)
lineq.pop()
# Add context, ignoring empty lines.
return TestFailure(line, check, self, after = [line.text for line in lineq[::-1] if not line.is_empty_space()])
# Drain empties.
while lineq and lineq[-1].is_empty_space():
lineq.pop()
@ -258,6 +276,13 @@ class TestRun(object):
close_fds=True, # For Python 2.6 as shipped on RHEL 6
)
stdout, stderr = proc.communicate()
# HACK: This is quite cheesy: POSIX specifies that sh should return 127 for a missing command.
# Technically it's also possible to return it in other conditions.
# Practically, that's *probably* not going to happen.
status = proc.returncode
if status == 127:
raise CheckerError("Command could not be found: " + self.subbed_command)
outlines = [
Line(text, idx + 1, "stdout")
for idx, text in enumerate(split_by_newlines(stdout))
@ -343,7 +368,12 @@ class Checker(object):
# Find run commands.
self.runcmds = [RunCmd.parse(sl) for sl in group1s(RUN_RE)]
if not self.runcmds:
raise CheckerError("No runlines ('# RUN') found")
# If no RUN command has been given, fall back to the shebang.
if lines[0].text.startswith("#!"):
# Remove the "#!" at the beginning, and the newline at the end.
self.runcmds = [RunCmd(lines[0].text[2:-1] + " %s", lines[0])]
else:
raise CheckerError("No runlines ('# RUN') found")
# Find check cmds.
self.outchecks = [
@ -406,7 +436,22 @@ def get_argparse():
action="append",
default=[],
)
parser.add_argument(
"-p",
"--progress",
action='store_true',
dest='progress',
help="Show the files to be checked",
default=False,
)
parser.add_argument("file", nargs="+", help="File to check")
parser.add_argument(
"-A", "--after",
type=int,
help="How many non-empty lines of output after a failure to print (default: 5)",
action="store",
default=5,
)
return parser
@ -419,11 +464,19 @@ def main():
success = True
config = Config()
config.colorize = sys.stdout.isatty()
config.progress = args.progress
fields = config.colors()
config.after = args.after
for path in args.file:
fields["path"] = path
if config.progress:
print("Testing file {path} ... ".format(**fields), end='')
subs = def_subs.copy()
subs["s"] = path
if not check_path(path, subs, config, TestFailure.print_message):
success = False
elif config.progress:
print("{GREEN}ok{RESET}".format(**fields))
sys.exit(0 if success else 1)