mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 14:03:58 +00:00
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:
parent
d910bada82
commit
25810b70f2
1 changed files with 58 additions and 5 deletions
|
@ -27,6 +27,10 @@ class Config(object):
|
||||||
self.verbose = False
|
self.verbose = False
|
||||||
# Whether output gets ANSI colorization.
|
# Whether output gets ANSI colorization.
|
||||||
self.colorize = False
|
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):
|
def colors(self):
|
||||||
""" Return a dictionary mapping color names to ANSI escapes """
|
""" Return a dictionary mapping color names to ANSI escapes """
|
||||||
|
@ -112,13 +116,16 @@ class RunCmd(object):
|
||||||
|
|
||||||
|
|
||||||
class TestFailure(object):
|
class TestFailure(object):
|
||||||
def __init__(self, line, check, testrun):
|
def __init__(self, line, check, testrun, after = None):
|
||||||
self.line = line
|
self.line = line
|
||||||
self.check = check
|
self.check = check
|
||||||
self.testrun = testrun
|
self.testrun = testrun
|
||||||
self.error_annotation_line = None
|
self.error_annotation_line = None
|
||||||
|
# The output that comes *after* the failure.
|
||||||
|
self.after = after
|
||||||
|
|
||||||
def message(self):
|
def message(self):
|
||||||
|
afterlines = self.testrun.config.after
|
||||||
fields = self.testrun.config.colors()
|
fields = self.testrun.config.colors()
|
||||||
fields["name"] = self.testrun.name
|
fields["name"] = self.testrun.name
|
||||||
fields["subbed_command"] = self.testrun.subbed_command
|
fields["subbed_command"] = self.testrun.subbed_command
|
||||||
|
@ -139,7 +146,8 @@ class TestFailure(object):
|
||||||
"check_type": self.check.type,
|
"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:
|
if self.line and self.check:
|
||||||
fmtstrs += [
|
fmtstrs += [
|
||||||
" The {check_type} on line {input_lineno} wants:",
|
" The {check_type} on line {input_lineno} wants:",
|
||||||
|
@ -171,6 +179,12 @@ class TestFailure(object):
|
||||||
" additional output on stderr:{error_annotation_lineno}:",
|
" additional output on stderr:{error_annotation_lineno}:",
|
||||||
" {BOLD}{error_annotation}{RESET}",
|
" {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}"]
|
fmtstrs += [" when running command:", " {subbed_command}"]
|
||||||
return "\n".join(fmtstrs).format(**fields)
|
return "\n".join(fmtstrs).format(**fields)
|
||||||
|
|
||||||
|
@ -194,7 +208,9 @@ def perform_substitution(input_str, subs):
|
||||||
for key, replacement in subs_ordered:
|
for key, replacement in subs_ordered:
|
||||||
if text.startswith(key):
|
if text.startswith(key):
|
||||||
return replacement + text[len(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)
|
return re.sub(r"%(%|[a-zA-Z0-9_-]+)", subber, input_str)
|
||||||
|
|
||||||
|
@ -224,7 +240,9 @@ class TestRun(object):
|
||||||
lineq.pop()
|
lineq.pop()
|
||||||
else:
|
else:
|
||||||
# Failed to match.
|
# 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.
|
# Drain empties.
|
||||||
while lineq and lineq[-1].is_empty_space():
|
while lineq and lineq[-1].is_empty_space():
|
||||||
lineq.pop()
|
lineq.pop()
|
||||||
|
@ -258,6 +276,13 @@ class TestRun(object):
|
||||||
close_fds=True, # For Python 2.6 as shipped on RHEL 6
|
close_fds=True, # For Python 2.6 as shipped on RHEL 6
|
||||||
)
|
)
|
||||||
stdout, stderr = proc.communicate()
|
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 = [
|
outlines = [
|
||||||
Line(text, idx + 1, "stdout")
|
Line(text, idx + 1, "stdout")
|
||||||
for idx, text in enumerate(split_by_newlines(stdout))
|
for idx, text in enumerate(split_by_newlines(stdout))
|
||||||
|
@ -343,7 +368,12 @@ class Checker(object):
|
||||||
# Find run commands.
|
# Find run commands.
|
||||||
self.runcmds = [RunCmd.parse(sl) for sl in group1s(RUN_RE)]
|
self.runcmds = [RunCmd.parse(sl) for sl in group1s(RUN_RE)]
|
||||||
if not self.runcmds:
|
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.
|
# Find check cmds.
|
||||||
self.outchecks = [
|
self.outchecks = [
|
||||||
|
@ -406,7 +436,22 @@ def get_argparse():
|
||||||
action="append",
|
action="append",
|
||||||
default=[],
|
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("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
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
@ -419,11 +464,19 @@ def main():
|
||||||
success = True
|
success = True
|
||||||
config = Config()
|
config = Config()
|
||||||
config.colorize = sys.stdout.isatty()
|
config.colorize = sys.stdout.isatty()
|
||||||
|
config.progress = args.progress
|
||||||
|
fields = config.colors()
|
||||||
|
config.after = args.after
|
||||||
for path in args.file:
|
for path in args.file:
|
||||||
|
fields["path"] = path
|
||||||
|
if config.progress:
|
||||||
|
print("Testing file {path} ... ".format(**fields), end='')
|
||||||
subs = def_subs.copy()
|
subs = def_subs.copy()
|
||||||
subs["s"] = path
|
subs["s"] = path
|
||||||
if not check_path(path, subs, config, TestFailure.print_message):
|
if not check_path(path, subs, config, TestFailure.print_message):
|
||||||
success = False
|
success = False
|
||||||
|
elif config.progress:
|
||||||
|
print("{GREEN}ok{RESET}".format(**fields))
|
||||||
sys.exit(0 if success else 1)
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue