From 9fc003bc8ad9d05024a94ccdb60c97efb5b2a924 Mon Sep 17 00:00:00 2001 From: Cassie Jones Date: Sun, 7 Feb 2021 02:24:35 -0500 Subject: [PATCH] [tools] Add tools/lit.py This tool is inspired by LLVM's lit, but is much less fancy. --- ivy-examples/break.vy | 5 ++ ivy-examples/fibonacci.vy | 30 ++++---- test/debug.vy | 5 ++ tools/lit.py | 154 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 test/debug.vy create mode 100755 tools/lit.py diff --git a/ivy-examples/break.vy b/ivy-examples/break.vy index 0353a36..1d1f6c8 100644 --- a/ivy-examples/break.vy +++ b/ivy-examples/break.vy @@ -1,5 +1,10 @@ +; RUN: run.py %s | filecheck %s + (let ( [base (lam (y) (debug y))] [f (lam (x) base)] [use (f 0 0)] ) (f use use)) + +; check: 0 +; nextln: 0 diff --git a/ivy-examples/fibonacci.vy b/ivy-examples/fibonacci.vy index 8140533..80aaf33 100644 --- a/ivy-examples/fibonacci.vy +++ b/ivy-examples/fibonacci.vy @@ -1,4 +1,19 @@ -; nextln: 0 +; RUN: run.py %s | filecheck %s + +(let ([nil (lam (c n) (n))] + [cons (lam (x y) (lam (c n) (c x y)))] + [head true] + [fix (lam (f) + (let ([t (lam (x) (f (lam (v) (x x v))))]) + (t t)))] + [if (lam (c t f) ((c t f)))]) + ([fix (lam (recur a b l n) + (if (<= n 0) + [lam () (let [(_ (debug 0))] l)] + [lam () (recur b (+ (debug a) b) (cons a l) (- n 1))]))] + 0 1 nil 30)) + +; check: 0 ; nextln: 1 ; nextln: 1 ; nextln: 2 @@ -29,16 +44,3 @@ ; nextln: 317811 ; nextln: 514229 ; nextln: 0 - -(let ([nil (lam (c n) (n))] - [cons (lam (x y) (lam (c n) (c x y)))] - [head true] - [fix (lam (f) - (let ([t (lam (x) (f (lam (v) (x x v))))]) - (t t)))] - [if (lam (c t f) ((c t f)))]) - ([fix (lam (recur a b l n) - (if (<= n 0) - [lam () (let [(_ (debug 0))] l)] - [lam () (recur b (+ (debug a) b) (cons a l) (- n 1))]))] - 0 1 nil 30)) diff --git a/test/debug.vy b/test/debug.vy new file mode 100644 index 0000000..2ea53da --- /dev/null +++ b/test/debug.vy @@ -0,0 +1,5 @@ +; RUN: run.py %s | filecheck %s + +(debug 413) + +; check: 413 diff --git a/tools/lit.py b/tools/lit.py new file mode 100755 index 0000000..ff3e498 --- /dev/null +++ b/tools/lit.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +import argparse +import os +import subprocess +import sys +import shlex +from pathlib import Path + + +ROOT = Path(__file__).parent.parent +TOOLS = ROOT / "tools" +TARGET_RELEASE = ROOT / "target" / "release" + + +IS_TTY = sys.stdout.isatty() + +def green(x): + if not IS_TTY: + return x + return f"\033[32m{x}\033[0m" + +def yellow(x): + if not IS_TTY: + return x + return f"\033[33m{x}\033[0m" + +def red(x): + if not IS_TTY: + return x + return f"\033[31m{x}\033[0m" + + +def relative(path): + return str(Path(path).relative_to(Path.cwd())) + + +def discover(roots): + def inspect(path): + with open(path, "r") as f: + run_lines = [line for line in f if "RUN:" in line] + if not run_lines: + return + run_lines = [line.split("RUN:")[1] for line in run_lines] + yield (path, {"RUN": run_lines}) + + for root in roots: + root = Path(root).resolve() + if root.is_file(): + yield from inspect(root) + continue + + for root, dirs, files in os.walk(root): + root = Path(root) + for fname in files: + path = root / fname + yield from inspect(path) + + +def split_seq(seq, split): + chunk = [] + for item in seq: + if item == split: + yield chunk + chunk = [] + else: + chunk.append(item) + yield chunk + + +def run_test(source, pipeline, verbose=False, PATH=None): + s = shlex.shlex(pipeline, posix=True, punctuation_chars=True) + s.whitespace_split = True + pipeline = list(split_seq(s, '|')) + env = {} + if PATH: + env["PATH"] = PATH + + print(f"{relative(source):<72}", end='', flush=True) + next_stdin = subprocess.PIPE + first_process = None + processes = [] + stderr_r, stderr = os.pipe() + for command in reversed(pipeline): + command = [word.replace("%s", str(source.resolve())) for word in command] + process = subprocess.Popen( + command, stdin=subprocess.PIPE, stderr=stderr, stdout=next_stdin, env=env + ) + next_stdin = process.stdin + processes.append(process) + processes.reverse() + processes[0].communicate() + any_failed = False + for process in processes: + process.stdin.close() + process.wait() + any_failed |= process.returncode != 0 + os.close(stderr) + if any_failed: + print(red('FAIL')) + if verbose: + print(yellow("stdout:")) + for line in processes[-1].stdout: + sys.stdout.buffer.write(line) + print() + print(yellow("stderr:")) + while chunk := os.read(stderr_r, 1024): + sys.stdout.buffer.write(chunk) + print() + return False + else: + print(green('PASS')) + return True + + + +def report(tests): + passes = [path for path, status in tests.items() if status == "PASS"] + failures = [path for path, status in tests.items() if status == "FAIL"] + print( + f""" +PASS: {len(passes)} +FAIL: {len(failures)} +""" + ) + if len(failures): + print(yellow("Failures:")) + print() + for failure in failures: + print(f" {relative(failure)}") + print(red("\nFAILED\n")) + return 1 + else: + print(green("PASSED\n")) + return 0 + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("tests", nargs='+') + parser.add_argument("-v", "--verbose", action='store_true') + args = parser.parse_args() + tests = {} + PATH = ":".join([os.getenv("PATH"), str(TOOLS), str(TARGET_RELEASE)]) + for path, directives in discover(args.tests): + tests[path] = "FAIL" + for run_line in directives["RUN"]: + if run_test(path, run_line, args.verbose, PATH): + tests[path] = "PASS" + status = report(tests) + sys.exit(status) + + +if __name__ == "__main__": + main() -- 2.43.2