summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Fitzgerald <fitzgen@gmail.com>2016-10-18 15:14:56 -0700
committerNick Fitzgerald <fitzgen@gmail.com>2016-10-18 15:57:10 -0700
commitffd8f27613438477dcc1a46030ad3d32bf765dd0 (patch)
tree3e142c6b7359e31aeb1a58633a7c858429d35b13
parent2d359fa2ac1a67f23bd20fdc856690d08862c4bc (diff)
Rewrite tests/tools/run-bindgen.py
Changes include: * Using argparse for argument parsing. This will help when extending the script to skip tests that will only pass when --features llvm_stable is used to build bindgen. * Pulling out all the various steps of the script into helper functions that are easier to digest at a glance, and have docstrings describing their role in the script. * Printing diffs between expected and actual generated bindings rather than the full source text of each.
-rwxr-xr-xtests/tools/run-bindgen.py187
1 files changed, 140 insertions, 47 deletions
diff --git a/tests/tools/run-bindgen.py b/tests/tools/run-bindgen.py
index 3ccea5c2..c9ddc4f2 100755
--- a/tests/tools/run-bindgen.py
+++ b/tests/tools/run-bindgen.py
@@ -1,69 +1,162 @@
#!/usr/bin/env python
+from __future__ import print_function
+
+import argparse
+import difflib
import os
import sys
import subprocess
import tempfile
BINDGEN_FLAGS_PREFIX = "// bindgen-flags: ";
-CLANG_FLAGS_SEPARATOR = "-- "
+
COMMON_PRELUDE = """
#![allow(non_snake_case)]
"""
-if len(sys.argv) != 4:
- print("Usage: {} [bindgen-path] [c-path] [rust-path]\n".format(sys.argv[0]))
- sys.exit(1)
+DESCRIPTION = """
+Run bindgen on a test header and check the generated bindings against expected
+output.
+"""
-[_, bindgen_path, c_path, rust_path] = sys.argv
+def make_parser():
+ """Make the commandline parser"""
+ parser = argparse.ArgumentParser(description=DESCRIPTION)
+ parser.add_argument("bindgen",
+ metavar="BINDGEN",
+ help="The path to the bindgen executable")
+ parser.add_argument("header",
+ metavar="HEADER",
+ help="The path to the input header")
+ parser.add_argument("rust_bindings",
+ metavar="RUST_BINDINGS",
+ help="The path to the generated rust output. If a file \
+ at this path already exists, the newly generated \
+ bindings will be checked against those extant \
+ expected bindings.")
+ return parser
-flags = ["--no-unstable-rust"]
+def usage_and_exit(*args):
+ """Print the program usage and exit. If args are given, print them first"""
+ if len(args) > 0:
+ print(*args)
+ make_parser().print_help()
+ sys.exit(1)
-with open(c_path) as f:
- for line in f:
- if line.startswith(BINDGEN_FLAGS_PREFIX):
- flags.extend(line.strip().split(BINDGEN_FLAGS_PREFIX)[1].split(" "))
- break
+def parse_args():
+ """Get, parse, and validate commandline arguments."""
+ parser = make_parser()
+ args = parser.parse_args()
-base_command = [bindgen_path, "-o", rust_path]
+ if not os.path.isfile(args.bindgen):
+ usage_and_exit("error: bindgen is not a file:", args.bindgen)
-for line in COMMON_PRELUDE.split("\n"):
- base_command.append("--raw-line")
- base_command.append(line)
+ if not os.path.isfile(args.header):
+ usage_and_exit("error: header is not a file:", args.header)
-base_command.extend(flags)
-base_command.append(c_path)
+ return args
-env = os.environ.copy()
+def make_bindgen_env():
+ """Build the environment to run bindgen in."""
+ env = os.environ.copy()
-# El Capitan likes to unset dyld variables
-# https://forums.developer.apple.com/thread/9233
-if "DYLD_LIBRARY_PATH" not in env and "LIBCLANG_PATH" in env:
+ # El Capitan likes to unset dyld variables
+ # https://forums.developer.apple.com/thread/9233
+ if "DYLD_LIBRARY_PATH" not in env and "LIBCLANG_PATH" in env:
env["DYLD_LIBRARY_PATH"] = env["LIBCLANG_PATH"]
-# If the rust file already exists, read it now so we can compare its contents
-# before and after.
-original_rust_contents = None
-if os.path.isfile(rust_path):
- with open(rust_path) as f:
- original_rust_contents = f.read()
-
-subprocess.check_call(base_command, cwd=os.getcwd(), env=env)
-
-name = None
-with tempfile.NamedTemporaryFile(delete=False) as tests:
- name = tests.name
- subprocess.check_call(["rustc", "--test", sys.argv[3], "-o", tests.name])
-subprocess.check_call([tests.name])
-
-if original_rust_contents is not None:
- new_rust_contents = None
- with open(rust_path) as f:
- new_rust_contents = f.read()
- if new_rust_contents != original_rust_contents:
- print("Generated rust bindings do not match expectation!")
- print("Expected rust bindings:")
- print(original_rust_contents)
- print("Actual rust bindings:")
- print(new_rust_contents)
- sys.exit(1)
+ return env
+
+def get_bindgen_flags_for_header(header_path):
+ """Get the flags to pass to bindgen for this header."""
+ flags = ["--no-unstable-rust"]
+
+ for line in COMMON_PRELUDE.split("\n"):
+ flags.append("--raw-line")
+ flags.append(line)
+
+ with open(header_path) as f:
+ for line in f:
+ if line.startswith(BINDGEN_FLAGS_PREFIX):
+ flags.extend(line.strip().split(BINDGEN_FLAGS_PREFIX)[1].split(" "))
+ break
+
+ return flags
+
+def get_expected_bindings(rust_bindings_path):
+ """
+ Get the expected, generated rust bindings output, or None if there is no
+ expected output yet.
+ """
+ expected_bindings = None
+ if os.path.isfile(rust_bindings_path):
+ with open(rust_bindings_path) as f:
+ expected_bindings = f.read()
+ return expected_bindings
+
+def get_actual_bindings(rust_bindings_path):
+ """Get the actual generated rust bindings output."""
+ assert os.path.isfile(rust_bindings_path)
+ with open(rust_bindings_path) as f:
+ return f.read()
+
+def run_cmd(command, **kwargs):
+ """Run the given command, passing through **kwargs to subprocess.check_call"""
+ print("run-bindgen.py: running", command)
+ subprocess.check_call(command, **kwargs)
+
+def generate_bindings(bindgen, header, output):
+ """Generate the rust bindings."""
+ command = [bindgen, "-o", output]
+ command.extend(get_bindgen_flags_for_header(header))
+ command.append(header)
+ run_cmd(command, cwd=os.getcwd(), env=make_bindgen_env())
+
+def test_generated_bindings(bindings):
+ """Run the generated bindings's #[test]s."""
+ name = None
+ # Do not delete the temp file, because we need to end the with block before
+ # we can run the tests.
+ with tempfile.NamedTemporaryFile(delete=False) as tests:
+ name = tests.name
+ run_cmd(["rustc", "--test", bindings, "-o", name])
+ run_cmd([name])
+
+def check_actual_vs_expected(expected_bindings, rust_bindings_path):
+ """
+ Check the actual generated rust bindings versus our expected generated rust
+ bindings. If they don't match up, print a diff between them and exit with a
+ failure.
+ """
+ if expected_bindings is None:
+ return
+
+ actual_bindings = get_actual_bindings(rust_bindings_path)
+ if actual_bindings == expected_bindings:
+ return
+
+ print("error: actual generated bindings do not match expected generated bindings!")
+
+ def to_diffable(s):
+ return map(lambda l: l + "\n", s.split("\n"))
+
+ diff = difflib.unified_diff(to_diffable(expected_bindings),
+ to_diffable(actual_bindings),
+ fromfile="expected_bindings.rs",
+ tofile="actual_bindings.rs")
+ sys.stderr.writelines(diff)
+ sys.stderr.write("\n")
+
+ sys.exit(1)
+
+def main():
+ args = parse_args()
+ expected_bindings = get_expected_bindings(args.rust_bindings)
+ generate_bindings(args.bindgen, args.header, args.rust_bindings)
+ test_generated_bindings(args.rust_bindings)
+ check_actual_vs_expected(expected_bindings, args.rust_bindings)
+ sys.exit(0)
+
+if __name__ == "__main__":
+ main()