mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
175 lines
4.7 KiB
Python
175 lines
4.7 KiB
Python
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
from pathlib import Path
|
|
|
|
from revng.cli.commands_registry import Command, Options
|
|
from revng.cli.revng import run_revng_command
|
|
|
|
import json
|
|
import os
|
|
import marko
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
|
|
from tempfile import NamedTemporaryFile
|
|
from collections import defaultdict
|
|
|
|
|
|
_verbose = False
|
|
|
|
def log(message):
|
|
if _verbose:
|
|
sys.stderr.write(message + "\n")
|
|
|
|
|
|
def only(list):
|
|
assert len(list) == 1
|
|
return list[0]
|
|
|
|
|
|
class PythonDoctest:
|
|
def __init__(self):
|
|
self.extension = ".py"
|
|
|
|
def command(self, path):
|
|
return ["python", "-c", "import doctest; import sys; doctest.testfile(sys.argv[-1])", path]
|
|
|
|
def process(self, example):
|
|
return example + "\n"
|
|
|
|
|
|
class BashDoctest:
|
|
def __init__(self):
|
|
self.extension = ".sh"
|
|
|
|
def command(self, path):
|
|
return ["docshtest", path]
|
|
|
|
def process(self, example):
|
|
return " " + example.replace("\n", "\n ") + "\n"
|
|
|
|
|
|
def escape_js(string):
|
|
assert type(string) is str
|
|
return json.dumps(string)
|
|
|
|
|
|
def emit_assertion(expected):
|
|
return f"if (JSON.stringify(last) !== {escape_js(expected[:-1])})\n process.exit(1);\n"
|
|
|
|
# WIP
|
|
class TypeScriptDoctest:
|
|
def __init__(self):
|
|
self.extension = ".ts"
|
|
|
|
def command(self, path):
|
|
return ["bash", "-c", f"""./node_modules/.bin/tsc "{path}" && node "{path.replace(".ts", ".js")}" && rm "{path.replace(".ts", ".js")}" """]
|
|
|
|
def process(self, example):
|
|
output = "console.log = (x) => { return x; };\nlet last;\n"
|
|
|
|
last_output = ""
|
|
lines = list(map(str.strip, example.split("\n")))[:-1]
|
|
for line, next_line in zip(lines, lines[1:] + ["> "]):
|
|
is_command = line.startswith("> ")
|
|
next_is_command = next_line.startswith("> ")
|
|
|
|
if is_command:
|
|
if last_output:
|
|
output += emit_assertion(last_output)
|
|
last_output = ""
|
|
|
|
line = line[2:]
|
|
if next_is_command:
|
|
output += f"{line}\n"
|
|
else:
|
|
output += f"last = {line}\n"
|
|
|
|
else:
|
|
last_output += line + "\n"
|
|
|
|
if last_output:
|
|
output += emit_assertion(last_output)
|
|
|
|
return output
|
|
|
|
|
|
|
|
def run(arguments):
|
|
log(f"Running {str(arguments)}")
|
|
return subprocess.run(arguments, check=True)
|
|
|
|
|
|
class TestDocsCommand(Command):
|
|
def __init__(self):
|
|
super().__init__(("test-docs",), "revng documentation tester")
|
|
|
|
|
|
def register_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--verbose", action="store_true", help="Be verbose."
|
|
)
|
|
|
|
parser.add_argument("files",
|
|
metavar="FILE",
|
|
nargs="*",
|
|
help="files to test.")
|
|
|
|
def run(self, options: Options):
|
|
args = options.parsed_args
|
|
|
|
global _verbose
|
|
_verbose = options.verbose
|
|
|
|
if args.files:
|
|
files = [Path(path) for path in args.files]
|
|
else:
|
|
files = []
|
|
for search_prefix in options.search_prefixes:
|
|
docs_path = Path(search_prefix) / "share" / "doc" / "revng"
|
|
reading_order_path = docs_path / "READING-ORDER"
|
|
if reading_order_path.exists():
|
|
files += [docs_path / docfile_path
|
|
for docfile_path
|
|
in reading_order_path.read_text().split("\n")
|
|
if docfile_path]
|
|
|
|
|
|
handlers = {
|
|
"python": PythonDoctest(),
|
|
"bash": BashDoctest(),
|
|
# "typescript": TypeScriptDoctest()
|
|
}
|
|
|
|
scripts = defaultdict(str)
|
|
|
|
for path in files:
|
|
document = marko.parse(path.read_text())
|
|
|
|
for block in document.children:
|
|
if type(block) is not marko.block.FencedCode:
|
|
continue
|
|
|
|
if block.lang not in handlers:
|
|
continue
|
|
|
|
rawtext = only(block.children)
|
|
assert type(rawtext) is marko.inline.RawText
|
|
text = rawtext.children
|
|
assert type(text) is str
|
|
|
|
scripts[block.lang] += handlers[block.lang].process(text)
|
|
|
|
for language, script in scripts.items():
|
|
handler = handlers[language]
|
|
|
|
with NamedTemporaryFile("w",
|
|
suffix=handler.extension,
|
|
dir=os.getcwd()) as temporary_file:
|
|
temporary_file.write(script)
|
|
temporary_file.flush()
|
|
run(handler.command(os.path.basename(temporary_file.name)))
|