[test] Compile python modules

Minor speedup.

Need to distribute them from meson.build I suppose.
diff --git a/test/shape/run-tests.py b/test/shape/run-tests.py
index 21b8327..2184a5f 100755
--- a/test/shape/run-tests.py
+++ b/test/shape/run-tests.py
@@ -1,332 +1,3 @@
 #!/usr/bin/env python3
 
-import sys, os, subprocess, hashlib
-
-args = sys.argv[1:]
-
-verbose = False
-if args and args[0] == "-v":
-    verbose = True
-    args = args[1:]
-
-if not args or args[0].find("hb-shape") == -1 or not os.path.exists(args[0]):
-    sys.exit("""First argument does not seem to point to usable hb-shape.""")
-hb_shape, args = args[0], args[1:]
-
-env = os.environ.copy()
-env["LC_ALL"] = "C"
-
-EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
-
-
-def open_shape_batch_process():
-    cmd = [hb_shape, "--batch"]
-    if EXE_WRAPPER:
-        cmd = [EXE_WRAPPER] + cmd
-
-    process = subprocess.Popen(
-        cmd,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        stderr=sys.stdout,
-        env=env,
-    )
-    return process
-
-
-shape_process = open_shape_batch_process()
-no_glyph_names_process = None
-
-
-def shape_cmd(command, shape_process, verbose=False):
-    global hb_shape
-
-    # (Re)start shaper if it is dead
-    if shape_process.poll() is not None:
-        shape_process = open_shape_batch_process()
-
-    if verbose:
-        print(hb_shape + " " + " ".join(command))
-    shape_process.stdin.write((";".join(command) + "\n").encode("utf-8"))
-    shape_process.stdin.flush()
-    return shape_process.stdout.readline().decode("utf-8").strip()
-
-
-def plural(what):
-    if not what.endswith("s"):
-        what += "s"
-    return what
-
-
-def whats_var_name(what):
-    return plural(what).replace("-", "_")
-
-
-def supported_whats_var_name(what):
-    whats = whats_var_name(what)
-    return "supported_" + whats
-
-
-def supported_whats(what):
-    return globals()[supported_whats_var_name(what)]
-
-
-def all_whats_var_name(what):
-    whats = whats_var_name(what)
-    return "all_" + whats
-
-
-def all_whats(what):
-    return globals()[all_whats_var_name(what)]
-
-
-# Collect supported backends
-for what in ["shaper", "face-loader", "font-funcs"]:
-    subcommand = "--list-" + plural(what)
-
-    cmd = [hb_shape, subcommand]
-    if EXE_WRAPPER:
-        cmd = [EXE_WRAPPER] + cmd
-
-    what_process = subprocess.Popen(
-        cmd,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        stderr=sys.stdout,
-        env=env,
-    )
-    # Capture the output
-    what_list = what_process.communicate()[0].decode("utf-8").strip().split()
-    if what_process.returncode:
-        sys.exit(f"Failed to run: {hb_shape} {subcommand}")
-    whats = plural(what)
-    var_name = supported_whats_var_name(what)
-    globals()[var_name] = what_list
-    print(f"Supported {whats}: {what_list}")
-
-# If running under Wine and not native dlls, make the respective shapers unavailable.
-if os.environ.get("WINEPATH"):
-    overrides = os.environ.get("WINEDLLOVERRIDES", "").lower()
-    if "directwrite" in supported_shapers and overrides.find("dwrite") == -1:
-        supported_shapers.remove("directwrite")
-        print("Skipping DirectWrite shaper under Wine.")
-    if "uniscribe" in supported_shapers and overrides.find("usp10") == -1:
-        supported_shapers.remove("uniscribe")
-        print("Skipping Uniscribe shaper under Wine.")
-
-
-passes = 0
-fails = 0
-skips = 0
-
-if not len(args):
-    args = ["-"]
-
-for filename in args:
-    if filename == "-":
-        print("Running tests from standard input")
-    else:
-        print("Running tests in " + filename)
-
-    if filename == "-":
-        f = sys.stdin
-    else:
-        f = open(filename, encoding="utf8")
-
-    # By default test all backends
-    for what in ["shaper", "face-loader", "font-funcs"]:
-        all_var_name = all_whats_var_name(what)
-        globals()[all_var_name] = supported_whats(what)
-    all_shapers = ["ot"]  # But only 'ot' shaper
-
-    # Right now we only test the 'ot' shaper if nothing specified,
-    # but try all font-funcs unless overriden.
-    # Only 'ot' face-loader is tested.
-
-    for line in f:
-        comment = False
-        if line.startswith("#"):
-            comment = True
-            line = line[1:]
-
-            if line.startswith(" "):
-                if verbose:
-                    print("#%s" % line)
-                continue
-
-        line = line.strip()
-        if not line:
-            continue
-
-        if line.startswith("@"):
-            # Directive
-            line = line.strip()
-            line = line.split("#")[0].strip()[1:]
-            consumed = False
-            for what in ["shaper", "face-loader", "font-funcs"]:
-                whats = plural(what)
-                if line.startswith(what) or line.startswith(whats):
-                    command, values = line.split("=")
-                    values = values.strip().split(",")
-
-                    supported = supported_whats(what)
-                    if command[-1] == "-":
-                        # Exclude
-                        values = [v for v in supported if v not in values]
-                    else:
-                        # Specify
-                        values = [v for v in values if v in supported]
-
-                    var_name = all_whats_var_name(what)
-                    print(f"Setting {whats} to test to {values}")
-                    globals()[var_name] = values
-                    consumed = True
-            if consumed:
-                print(line)
-                continue
-            else:
-                print("Unrecognized directive: %s" % line, file=sys.stderr)
-                sys.exit(1)
-
-        fontfile, options, unicodes, glyphs_expected = line.split(";")
-        options = options.split()
-        if fontfile.startswith("/") or fontfile.startswith('"/'):
-            if os.name == "nt":  # Skip on Windows
-                continue
-
-            fontfile, expected_hash = (fontfile.split("@") + [""])[:2]
-
-            try:
-                with open(fontfile, "rb") as ff:
-                    if expected_hash:
-                        actual_hash = hashlib.sha1(ff.read()).hexdigest().strip()
-                        if actual_hash != expected_hash:
-                            print(
-                                "different version of %s found; Expected hash %s, got %s; skipping."
-                                % (fontfile, expected_hash, actual_hash)
-                            )
-                            skips += 1
-                            continue
-            except IOError:
-                print("%s not found, skip." % fontfile)
-                skips += 1
-                continue
-        else:
-            cwd = os.path.dirname(filename)
-            fontfile = os.path.normpath(os.path.join(cwd, fontfile))
-
-        if comment:
-            if verbose:
-                print('# %s "%s" --unicodes %s' % (hb_shape, fontfile, unicodes))
-            continue
-
-        skip_test = False
-        shaper = None
-        face_loader = None
-        font_funcs = None
-        new_options = []
-        it = iter(options)
-        for option in it:
-            consumed = False
-            for what in ["shaper", "face-loader", "font-funcs"]:
-                if option.startswith("--" + what):
-                    try:
-                        backend = option.split("=")[1]
-                    except IndexError:
-                        backend = next(it)
-                    if backend not in supported_whats(what):
-                        skips += 1
-                        print(f"Skipping test with {what}={backend}.")
-                        skip_test = True
-                        break
-                    what = what.replace("-", "_")
-                    globals()[what] = backend
-                    consumed = True
-            if not consumed:
-                new_options.append(option)
-
-            if skip_test:
-                break
-        if skip_test:
-            continue
-        options = new_options
-
-        for shaper in [shaper] if shaper else all_whats("shaper"):
-            for font_funcs in [font_funcs] if font_funcs else all_whats("font-funcs"):
-                extra_options = []
-
-                if shaper:
-                    extra_options.append("--shaper=" + shaper)
-                if face_loader:
-                    extra_options.append("--face-loader=" + face_loader)
-                if font_funcs:
-                    extra_options.append("--font-funcs=" + font_funcs)
-
-                if glyphs_expected != "*":
-                    extra_options.append("--verify")
-                    extra_options.append("--unsafe-to-concat")
-
-                if verbose:
-                    print(
-                        "# shaper=%s face-loader=%s font-funcs=%s"
-                        % (shaper, face_loader, font_funcs)
-                    )
-                cmd = [fontfile] + ["--unicodes", unicodes] + options + extra_options
-                glyphs = shape_cmd(cmd, shape_process, verbose).strip()
-
-                if glyphs_expected == "*":
-                    passes += 1
-                    continue
-
-                final_glyphs = glyphs
-                final_glyphs_expected = glyphs_expected
-
-                if glyphs != glyphs_expected and glyphs.find("gid") != -1:
-                    if not no_glyph_names_process:
-                        no_glyph_names_process = open_shape_batch_process()
-
-                    cmd2 = [fontfile] + ["--glyphs", "--no-glyph-names", glyphs]
-                    final_glyphs = shape_cmd(cmd2, no_glyph_names_process).strip()
-
-                    cmd2 = [fontfile] + [
-                        "--glyphs",
-                        "--no-glyph-names",
-                        glyphs_expected,
-                    ]
-                    final_glyphs_expected = shape_cmd(
-                        cmd2, no_glyph_names_process
-                    ).strip()
-
-                # If the removal of glyph_ids failed, fail the test.
-                # https://github.com/harfbuzz/harfbuzz/issues/5169
-                if not final_glyphs_expected or final_glyphs != final_glyphs_expected:
-                    print(hb_shape + " " + " ".join(cmd), file=sys.stderr)
-                    print("Actual:   " + glyphs, file=sys.stderr)
-                    print("Expected: " + glyphs_expected, file=sys.stderr)
-                    if final_glyphs != glyphs:
-                        print(
-                            "Actual (no glyph names):   " + final_glyphs,
-                            file=sys.stderr,
-                        )
-                        print(
-                            "Expected (no glyph names): " + final_glyphs_expected,
-                            file=sys.stderr,
-                        )
-                    fails += 1
-                else:
-                    passes += 1
-
-print(
-    "%d tests passed; %d failed; %d skipped." % (passes, fails, skips), file=sys.stderr
-)
-if not (fails + passes):
-    print("No tests ran.")
-elif not (fails + skips):
-    print("All tests passed.")
-
-if fails:
-    sys.exit(1)
-elif passes:
-    sys.exit(0)
-else:
-    sys.exit(77)
+import run_tests_module
diff --git a/test/shape/run_tests_module.py b/test/shape/run_tests_module.py
new file mode 100644
index 0000000..7871bd5
--- /dev/null
+++ b/test/shape/run_tests_module.py
@@ -0,0 +1,330 @@
+import sys, os, subprocess, hashlib
+
+args = sys.argv[1:]
+
+verbose = False
+if args and args[0] == "-v":
+    verbose = True
+    args = args[1:]
+
+if not args or args[0].find("hb-shape") == -1 or not os.path.exists(args[0]):
+    sys.exit("""First argument does not seem to point to usable hb-shape.""")
+hb_shape, args = args[0], args[1:]
+
+env = os.environ.copy()
+env["LC_ALL"] = "C"
+
+EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
+
+
+def open_shape_batch_process():
+    cmd = [hb_shape, "--batch"]
+    if EXE_WRAPPER:
+        cmd = [EXE_WRAPPER] + cmd
+
+    process = subprocess.Popen(
+        cmd,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=sys.stdout,
+        env=env,
+    )
+    return process
+
+
+shape_process = open_shape_batch_process()
+no_glyph_names_process = None
+
+
+def shape_cmd(command, shape_process, verbose=False):
+    global hb_shape
+
+    # (Re)start shaper if it is dead
+    if shape_process.poll() is not None:
+        shape_process = open_shape_batch_process()
+
+    if verbose:
+        print(hb_shape + " " + " ".join(command))
+    shape_process.stdin.write((";".join(command) + "\n").encode("utf-8"))
+    shape_process.stdin.flush()
+    return shape_process.stdout.readline().decode("utf-8").strip()
+
+
+def plural(what):
+    if not what.endswith("s"):
+        what += "s"
+    return what
+
+
+def whats_var_name(what):
+    return plural(what).replace("-", "_")
+
+
+def supported_whats_var_name(what):
+    whats = whats_var_name(what)
+    return "supported_" + whats
+
+
+def supported_whats(what):
+    return globals()[supported_whats_var_name(what)]
+
+
+def all_whats_var_name(what):
+    whats = whats_var_name(what)
+    return "all_" + whats
+
+
+def all_whats(what):
+    return globals()[all_whats_var_name(what)]
+
+
+# Collect supported backends
+for what in ["shaper", "face-loader", "font-funcs"]:
+    subcommand = "--list-" + plural(what)
+
+    cmd = [hb_shape, subcommand]
+    if EXE_WRAPPER:
+        cmd = [EXE_WRAPPER] + cmd
+
+    what_process = subprocess.Popen(
+        cmd,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=sys.stdout,
+        env=env,
+    )
+    # Capture the output
+    what_list = what_process.communicate()[0].decode("utf-8").strip().split()
+    if what_process.returncode:
+        sys.exit(f"Failed to run: {hb_shape} {subcommand}")
+    whats = plural(what)
+    var_name = supported_whats_var_name(what)
+    globals()[var_name] = what_list
+    print(f"Supported {whats}: {what_list}")
+
+# If running under Wine and not native dlls, make the respective shapers unavailable.
+if os.environ.get("WINEPATH"):
+    overrides = os.environ.get("WINEDLLOVERRIDES", "").lower()
+    if "directwrite" in supported_shapers and overrides.find("dwrite") == -1:
+        supported_shapers.remove("directwrite")
+        print("Skipping DirectWrite shaper under Wine.")
+    if "uniscribe" in supported_shapers and overrides.find("usp10") == -1:
+        supported_shapers.remove("uniscribe")
+        print("Skipping Uniscribe shaper under Wine.")
+
+
+passes = 0
+fails = 0
+skips = 0
+
+if not len(args):
+    args = ["-"]
+
+for filename in args:
+    if filename == "-":
+        print("Running tests from standard input")
+    else:
+        print("Running tests in " + filename)
+
+    if filename == "-":
+        f = sys.stdin
+    else:
+        f = open(filename, encoding="utf8")
+
+    # By default test all backends
+    for what in ["shaper", "face-loader", "font-funcs"]:
+        all_var_name = all_whats_var_name(what)
+        globals()[all_var_name] = supported_whats(what)
+    all_shapers = ["ot"]  # But only 'ot' shaper
+
+    # Right now we only test the 'ot' shaper if nothing specified,
+    # but try all font-funcs unless overriden.
+    # Only 'ot' face-loader is tested.
+
+    for line in f:
+        comment = False
+        if line.startswith("#"):
+            comment = True
+            line = line[1:]
+
+            if line.startswith(" "):
+                if verbose:
+                    print("#%s" % line)
+                continue
+
+        line = line.strip()
+        if not line:
+            continue
+
+        if line.startswith("@"):
+            # Directive
+            line = line.strip()
+            line = line.split("#")[0].strip()[1:]
+            consumed = False
+            for what in ["shaper", "face-loader", "font-funcs"]:
+                whats = plural(what)
+                if line.startswith(what) or line.startswith(whats):
+                    command, values = line.split("=")
+                    values = values.strip().split(",")
+
+                    supported = supported_whats(what)
+                    if command[-1] == "-":
+                        # Exclude
+                        values = [v for v in supported if v not in values]
+                    else:
+                        # Specify
+                        values = [v for v in values if v in supported]
+
+                    var_name = all_whats_var_name(what)
+                    print(f"Setting {whats} to test to {values}")
+                    globals()[var_name] = values
+                    consumed = True
+            if consumed:
+                print(line)
+                continue
+            else:
+                print("Unrecognized directive: %s" % line, file=sys.stderr)
+                sys.exit(1)
+
+        fontfile, options, unicodes, glyphs_expected = line.split(";")
+        options = options.split()
+        if fontfile.startswith("/") or fontfile.startswith('"/'):
+            if os.name == "nt":  # Skip on Windows
+                continue
+
+            fontfile, expected_hash = (fontfile.split("@") + [""])[:2]
+
+            try:
+                with open(fontfile, "rb") as ff:
+                    if expected_hash:
+                        actual_hash = hashlib.sha1(ff.read()).hexdigest().strip()
+                        if actual_hash != expected_hash:
+                            print(
+                                "different version of %s found; Expected hash %s, got %s; skipping."
+                                % (fontfile, expected_hash, actual_hash)
+                            )
+                            skips += 1
+                            continue
+            except IOError:
+                print("%s not found, skip." % fontfile)
+                skips += 1
+                continue
+        else:
+            cwd = os.path.dirname(filename)
+            fontfile = os.path.normpath(os.path.join(cwd, fontfile))
+
+        if comment:
+            if verbose:
+                print('# %s "%s" --unicodes %s' % (hb_shape, fontfile, unicodes))
+            continue
+
+        skip_test = False
+        shaper = None
+        face_loader = None
+        font_funcs = None
+        new_options = []
+        it = iter(options)
+        for option in it:
+            consumed = False
+            for what in ["shaper", "face-loader", "font-funcs"]:
+                if option.startswith("--" + what):
+                    try:
+                        backend = option.split("=")[1]
+                    except IndexError:
+                        backend = next(it)
+                    if backend not in supported_whats(what):
+                        skips += 1
+                        print(f"Skipping test with {what}={backend}.")
+                        skip_test = True
+                        break
+                    what = what.replace("-", "_")
+                    globals()[what] = backend
+                    consumed = True
+            if not consumed:
+                new_options.append(option)
+
+            if skip_test:
+                break
+        if skip_test:
+            continue
+        options = new_options
+
+        for shaper in [shaper] if shaper else all_whats("shaper"):
+            for font_funcs in [font_funcs] if font_funcs else all_whats("font-funcs"):
+                extra_options = []
+
+                if shaper:
+                    extra_options.append("--shaper=" + shaper)
+                if face_loader:
+                    extra_options.append("--face-loader=" + face_loader)
+                if font_funcs:
+                    extra_options.append("--font-funcs=" + font_funcs)
+
+                if glyphs_expected != "*":
+                    extra_options.append("--verify")
+                    extra_options.append("--unsafe-to-concat")
+
+                if verbose:
+                    print(
+                        "# shaper=%s face-loader=%s font-funcs=%s"
+                        % (shaper, face_loader, font_funcs)
+                    )
+                cmd = [fontfile] + ["--unicodes", unicodes] + options + extra_options
+                glyphs = shape_cmd(cmd, shape_process, verbose).strip()
+
+                if glyphs_expected == "*":
+                    passes += 1
+                    continue
+
+                final_glyphs = glyphs
+                final_glyphs_expected = glyphs_expected
+
+                if glyphs != glyphs_expected and glyphs.find("gid") != -1:
+                    if not no_glyph_names_process:
+                        no_glyph_names_process = open_shape_batch_process()
+
+                    cmd2 = [fontfile] + ["--glyphs", "--no-glyph-names", glyphs]
+                    final_glyphs = shape_cmd(cmd2, no_glyph_names_process).strip()
+
+                    cmd2 = [fontfile] + [
+                        "--glyphs",
+                        "--no-glyph-names",
+                        glyphs_expected,
+                    ]
+                    final_glyphs_expected = shape_cmd(
+                        cmd2, no_glyph_names_process
+                    ).strip()
+
+                # If the removal of glyph_ids failed, fail the test.
+                # https://github.com/harfbuzz/harfbuzz/issues/5169
+                if not final_glyphs_expected or final_glyphs != final_glyphs_expected:
+                    print(hb_shape + " " + " ".join(cmd), file=sys.stderr)
+                    print("Actual:   " + glyphs, file=sys.stderr)
+                    print("Expected: " + glyphs_expected, file=sys.stderr)
+                    if final_glyphs != glyphs:
+                        print(
+                            "Actual (no glyph names):   " + final_glyphs,
+                            file=sys.stderr,
+                        )
+                        print(
+                            "Expected (no glyph names): " + final_glyphs_expected,
+                            file=sys.stderr,
+                        )
+                    fails += 1
+                else:
+                    passes += 1
+
+print(
+    "%d tests passed; %d failed; %d skipped." % (passes, fails, skips), file=sys.stderr
+)
+if not (fails + passes):
+    print("No tests ran.")
+elif not (fails + skips):
+    print("All tests passed.")
+
+if fails:
+    sys.exit(1)
+elif passes:
+    sys.exit(0)
+else:
+    sys.exit(77)
diff --git a/test/subset/run-repack-tests.py b/test/subset/run-repack-tests.py
index a0389e5..406374b 100755
--- a/test/subset/run-repack-tests.py
+++ b/test/subset/run-repack-tests.py
@@ -1,131 +1,3 @@
 #!/usr/bin/env python3
 
-# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz
-# to subsetting via fonttools.
-
-from difflib import unified_diff
-import os
-import re
-import subprocess
-import sys
-import tempfile
-import shutil
-import io
-
-from repack_test import RepackTest
-
-try:
-    from fontTools.ttLib import TTFont
-except ImportError:
-    print("fonttools is not present, skipping test.")
-    sys.exit(77)
-
-ots_sanitize = shutil.which("ots-sanitize")
-
-EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
-
-
-def subset_cmd(command):
-    global hb_subset, process
-    print(hb_subset + " " + " ".join(command))
-    process.stdin.write((";".join(command) + "\n").encode("utf-8"))
-    process.stdin.flush()
-    return process.stdout.readline().decode("utf-8").strip()
-
-
-def cmd(command):
-    p = subprocess.Popen(
-        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
-    )
-    (stdoutdata, stderrdata) = p.communicate()
-    print(stderrdata, end="", file=sys.stderr)
-    return stdoutdata, p.returncode
-
-
-def fail_test(test, cli_args, message):
-    print("ERROR: %s" % message)
-    print("Test State:")
-    print("  test.font_name    %s" % test.font_name)
-    print("  test.test_path %s" % os.path.abspath(test.test_path))
-    return 1
-
-
-def run_test(test, should_check_ots):
-    out_file = os.path.join(out_dir, test.font_name + "-subset.ttf")
-    cli_args = [
-        "--font-file=" + test.font_path(),
-        "--output-file=" + out_file,
-        "--unicodes=%s" % test.codepoints_string(),
-        "--drop-tables-=GPOS,GSUB,GDEF",
-    ]
-    print(" ".join(cli_args))
-    ret = subset_cmd(cli_args)
-
-    if ret != "success":
-        return fail_test(test, cli_args, "%s failed" % " ".join(cli_args))
-
-    try:
-        with TTFont(out_file) as font:
-            pass
-    except Exception as e:
-        print(e)
-        return fail_test(test, cli_args, "ttx failed to parse the result")
-
-    if should_check_ots:
-        print("Checking output with ots-sanitize.")
-        if not check_ots(out_file):
-            return fail_test(test, cli_args, "ots for subsetted file fails.")
-
-    return 0
-
-
-def has_ots():
-    if not ots_sanitize:
-        print("OTS is not present, skipping all ots checks.")
-        return False
-    return True
-
-
-def check_ots(path):
-    ots_report, returncode = cmd([ots_sanitize, path])
-    if returncode:
-        print("OTS Failure: %s" % ots_report)
-        return False
-    return True
-
-
-args = sys.argv[1:]
-if not args or sys.argv[1].find("hb-subset") == -1 or not os.path.exists(sys.argv[1]):
-    sys.exit("First argument does not seem to point to usable hb-subset.")
-hb_subset, args = args[0], args[1:]
-
-if len(args) != 1:
-    sys.exit("No tests supplied.")
-
-has_ots = has_ots()
-
-batch_cmd = [hb_subset, "--batch"]
-if EXE_WRAPPER:
-    batch_cmd = [EXE_WRAPPER] + batch_cmd
-process = subprocess.Popen(
-    batch_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stdout
-)
-
-fails = 0
-
-path = args[0]
-if not path.endswith(".tests"):
-    sys.exit("Not a valid test case path.")
-
-out_dir = tempfile.mkdtemp()
-
-with open(path, mode="r", encoding="utf-8") as f:
-    # TODO(garretrieger): re-enable OTS checking.
-    fails += run_test(RepackTest(path, f.read()), False)
-
-
-if fails != 0:
-    sys.exit("%d test(s) failed; output left in %s" % (fails, out_dir))
-else:
-    print("All tests passed.")
-    shutil.rmtree(out_dir)
+import run_repack_tests_module
diff --git a/test/subset/run-tests.py b/test/subset/run-tests.py
index b403bfa..2184a5f 100755
--- a/test/subset/run-tests.py
+++ b/test/subset/run-tests.py
@@ -1,207 +1,3 @@
 #!/usr/bin/env python3
 
-# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz
-# to subsetting via fonttools.
-
-from difflib import unified_diff
-import os
-import re
-import subprocess
-import sys
-import tempfile
-import shutil
-import io
-
-from subset_test_suite import SubsetTestSuite
-
-try:
-    from fontTools.ttLib import TTFont
-except ImportError:
-    TTFont = None
-
-ots_sanitize = shutil.which("ots-sanitize")
-
-
-def subset_cmd(command):
-    global hb_subset, subset_process
-
-    # (Re)start shaper if it is dead
-    if subset_process.poll() is not None:
-        subset_process = open_subset_batch_process()
-
-    print(hb_subset + " " + " ".join(command))
-    subset_process.stdin.write((";".join(command) + "\n").encode("utf-8"))
-    subset_process.stdin.flush()
-    return subset_process.stdout.readline().decode("utf-8").strip()
-
-
-def cmd(command):
-    p = subprocess.Popen(
-        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
-    )
-    (stdoutdata, stderrdata) = p.communicate()
-    print(stderrdata, end="", file=sys.stderr)
-    return stdoutdata, p.returncode
-
-
-def fail_test(test, cli_args, message):
-    print("ERROR: %s" % message)
-    print("Test State:")
-    print("  test.font_path    %s" % os.path.abspath(test.font_path))
-    print("  test.profile_path %s" % os.path.abspath(test.profile_path))
-    print("  test.unicodes        %s" % test.unicodes())
-    expected_file = os.path.join(
-        test_suite.get_output_directory(), test.get_font_name()
-    )
-    print("  expected_file        %s" % os.path.abspath(expected_file))
-    return 1
-
-
-def run_test(test, should_check_ots, preprocess):
-    out_file = os.path.join(
-        out_dir, test.get_font_name() + "-subset" + test.get_font_extension()
-    )
-    cli_args = [
-        "--font-file=" + test.font_path,
-        "--output-file=" + out_file,
-        "--unicodes=%s" % test.unicodes(),
-        "--drop-tables+=DSIG,BASE",
-        "--drop-tables-=sbix",
-    ]
-    if preprocess:
-        cli_args.extend(
-            [
-                "--preprocess",
-            ]
-        )
-
-    cli_args.extend(test.get_profile_flags())
-    if test.get_instance_flags():
-        cli_args.extend(["--instance=%s" % ",".join(test.get_instance_flags())])
-    if test.iup_optimize:
-        cli_args.extend(
-            [
-                "--optimize",
-            ]
-        )
-    ret = subset_cmd(cli_args)
-
-    if ret != "success":
-        return fail_test(test, cli_args, "%s failed" % " ".join(cli_args))
-
-    expected_file = os.path.join(
-        test_suite.get_output_directory(), test.get_font_name()
-    )
-    with open(expected_file, "rb") as fp:
-        expected_contents = fp.read()
-    with open(out_file, "rb") as fp:
-        actual_contents = fp.read()
-
-    if expected_contents == actual_contents:
-        if should_check_ots:
-            print("Checking output with ots-sanitize.")
-            if not check_ots(out_file):
-                return fail_test(test, cli_args, "ots for subsetted file fails.")
-        return 0
-
-    if TTFont is None:
-        print("fonttools is not present, skipping TTX diff.")
-        return fail_test(test, cli_args, "hash for expected and actual does not match.")
-
-    with io.StringIO() as fp:
-        try:
-            with TTFont(expected_file) as font:
-                font.saveXML(fp)
-        except Exception as e:
-            print(e)
-            return fail_test(test, cli_args, "ttx failed to parse the expected result")
-        expected_ttx = fp.getvalue()
-
-    with io.StringIO() as fp:
-        try:
-            with TTFont(out_file) as font:
-                font.saveXML(fp)
-        except Exception as e:
-            print(e)
-            return fail_test(test, cli_args, "ttx failed to parse the actual result")
-        actual_ttx = fp.getvalue()
-
-    if actual_ttx != expected_ttx:
-        for line in unified_diff(expected_ttx.splitlines(1), actual_ttx.splitlines(1)):
-            sys.stdout.write(line)
-        sys.stdout.flush()
-        return fail_test(test, cli_args, "ttx for expected and actual does not match.")
-
-    return fail_test(
-        test,
-        cli_args,
-        "hash for expected and actual does not match, "
-        "but the ttx matches. Expected file needs to be updated?",
-    )
-
-
-def has_ots():
-    if not ots_sanitize:
-        print("OTS is not present, skipping all ots checks.")
-        return False
-    return True
-
-
-def check_ots(path):
-    ots_report, returncode = cmd([ots_sanitize, path])
-    if returncode:
-        print("OTS Failure: %s" % ots_report)
-        return False
-    return True
-
-
-args = sys.argv[1:]
-if not args or sys.argv[1].find("hb-subset") == -1 or not os.path.exists(sys.argv[1]):
-    sys.exit("First argument does not seem to point to usable hb-subset.")
-hb_subset, args = args[0], args[1:]
-
-if not len(args):
-    sys.exit("No tests supplied.")
-
-has_ots = has_ots()
-
-env = os.environ.copy()
-env["LC_ALL"] = "C"
-
-EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
-
-
-def open_subset_batch_process():
-    cmd = [hb_subset, "--batch"]
-    if EXE_WRAPPER:
-        cmd = [EXE_WRAPPER] + cmd
-
-    process = subprocess.Popen(
-        cmd,
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        stderr=sys.stdout,
-        env=env,
-    )
-    return process
-
-
-subset_process = open_subset_batch_process()
-out_dir = tempfile.mkdtemp()
-
-fails = 0
-for path in args:
-    with open(path, mode="r", encoding="utf-8") as f:
-        print("Running tests in " + path)
-        test_suite = SubsetTestSuite(path, f.read())
-        for test in test_suite.tests():
-            # Tests are run with and without preprocessing, results should be the
-            # same between them.
-            fails += run_test(test, has_ots, False)
-            fails += run_test(test, has_ots, True)
-
-if fails != 0:
-    sys.exit("%d test(s) failed; output left in %s" % (fails, out_dir))
-else:
-    print("All tests passed.")
-    shutil.rmtree(out_dir)
+import run_tests_module
diff --git a/test/subset/run_repack_tests_module.py b/test/subset/run_repack_tests_module.py
new file mode 100755
index 0000000..a0389e5
--- /dev/null
+++ b/test/subset/run_repack_tests_module.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+
+# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz
+# to subsetting via fonttools.
+
+from difflib import unified_diff
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import shutil
+import io
+
+from repack_test import RepackTest
+
+try:
+    from fontTools.ttLib import TTFont
+except ImportError:
+    print("fonttools is not present, skipping test.")
+    sys.exit(77)
+
+ots_sanitize = shutil.which("ots-sanitize")
+
+EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
+
+
+def subset_cmd(command):
+    global hb_subset, process
+    print(hb_subset + " " + " ".join(command))
+    process.stdin.write((";".join(command) + "\n").encode("utf-8"))
+    process.stdin.flush()
+    return process.stdout.readline().decode("utf-8").strip()
+
+
+def cmd(command):
+    p = subprocess.Popen(
+        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
+    )
+    (stdoutdata, stderrdata) = p.communicate()
+    print(stderrdata, end="", file=sys.stderr)
+    return stdoutdata, p.returncode
+
+
+def fail_test(test, cli_args, message):
+    print("ERROR: %s" % message)
+    print("Test State:")
+    print("  test.font_name    %s" % test.font_name)
+    print("  test.test_path %s" % os.path.abspath(test.test_path))
+    return 1
+
+
+def run_test(test, should_check_ots):
+    out_file = os.path.join(out_dir, test.font_name + "-subset.ttf")
+    cli_args = [
+        "--font-file=" + test.font_path(),
+        "--output-file=" + out_file,
+        "--unicodes=%s" % test.codepoints_string(),
+        "--drop-tables-=GPOS,GSUB,GDEF",
+    ]
+    print(" ".join(cli_args))
+    ret = subset_cmd(cli_args)
+
+    if ret != "success":
+        return fail_test(test, cli_args, "%s failed" % " ".join(cli_args))
+
+    try:
+        with TTFont(out_file) as font:
+            pass
+    except Exception as e:
+        print(e)
+        return fail_test(test, cli_args, "ttx failed to parse the result")
+
+    if should_check_ots:
+        print("Checking output with ots-sanitize.")
+        if not check_ots(out_file):
+            return fail_test(test, cli_args, "ots for subsetted file fails.")
+
+    return 0
+
+
+def has_ots():
+    if not ots_sanitize:
+        print("OTS is not present, skipping all ots checks.")
+        return False
+    return True
+
+
+def check_ots(path):
+    ots_report, returncode = cmd([ots_sanitize, path])
+    if returncode:
+        print("OTS Failure: %s" % ots_report)
+        return False
+    return True
+
+
+args = sys.argv[1:]
+if not args or sys.argv[1].find("hb-subset") == -1 or not os.path.exists(sys.argv[1]):
+    sys.exit("First argument does not seem to point to usable hb-subset.")
+hb_subset, args = args[0], args[1:]
+
+if len(args) != 1:
+    sys.exit("No tests supplied.")
+
+has_ots = has_ots()
+
+batch_cmd = [hb_subset, "--batch"]
+if EXE_WRAPPER:
+    batch_cmd = [EXE_WRAPPER] + batch_cmd
+process = subprocess.Popen(
+    batch_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stdout
+)
+
+fails = 0
+
+path = args[0]
+if not path.endswith(".tests"):
+    sys.exit("Not a valid test case path.")
+
+out_dir = tempfile.mkdtemp()
+
+with open(path, mode="r", encoding="utf-8") as f:
+    # TODO(garretrieger): re-enable OTS checking.
+    fails += run_test(RepackTest(path, f.read()), False)
+
+
+if fails != 0:
+    sys.exit("%d test(s) failed; output left in %s" % (fails, out_dir))
+else:
+    print("All tests passed.")
+    shutil.rmtree(out_dir)
diff --git a/test/subset/run_tests_module.py b/test/subset/run_tests_module.py
new file mode 100755
index 0000000..b403bfa
--- /dev/null
+++ b/test/subset/run_tests_module.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+
+# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz
+# to subsetting via fonttools.
+
+from difflib import unified_diff
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import shutil
+import io
+
+from subset_test_suite import SubsetTestSuite
+
+try:
+    from fontTools.ttLib import TTFont
+except ImportError:
+    TTFont = None
+
+ots_sanitize = shutil.which("ots-sanitize")
+
+
+def subset_cmd(command):
+    global hb_subset, subset_process
+
+    # (Re)start shaper if it is dead
+    if subset_process.poll() is not None:
+        subset_process = open_subset_batch_process()
+
+    print(hb_subset + " " + " ".join(command))
+    subset_process.stdin.write((";".join(command) + "\n").encode("utf-8"))
+    subset_process.stdin.flush()
+    return subset_process.stdout.readline().decode("utf-8").strip()
+
+
+def cmd(command):
+    p = subprocess.Popen(
+        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
+    )
+    (stdoutdata, stderrdata) = p.communicate()
+    print(stderrdata, end="", file=sys.stderr)
+    return stdoutdata, p.returncode
+
+
+def fail_test(test, cli_args, message):
+    print("ERROR: %s" % message)
+    print("Test State:")
+    print("  test.font_path    %s" % os.path.abspath(test.font_path))
+    print("  test.profile_path %s" % os.path.abspath(test.profile_path))
+    print("  test.unicodes        %s" % test.unicodes())
+    expected_file = os.path.join(
+        test_suite.get_output_directory(), test.get_font_name()
+    )
+    print("  expected_file        %s" % os.path.abspath(expected_file))
+    return 1
+
+
+def run_test(test, should_check_ots, preprocess):
+    out_file = os.path.join(
+        out_dir, test.get_font_name() + "-subset" + test.get_font_extension()
+    )
+    cli_args = [
+        "--font-file=" + test.font_path,
+        "--output-file=" + out_file,
+        "--unicodes=%s" % test.unicodes(),
+        "--drop-tables+=DSIG,BASE",
+        "--drop-tables-=sbix",
+    ]
+    if preprocess:
+        cli_args.extend(
+            [
+                "--preprocess",
+            ]
+        )
+
+    cli_args.extend(test.get_profile_flags())
+    if test.get_instance_flags():
+        cli_args.extend(["--instance=%s" % ",".join(test.get_instance_flags())])
+    if test.iup_optimize:
+        cli_args.extend(
+            [
+                "--optimize",
+            ]
+        )
+    ret = subset_cmd(cli_args)
+
+    if ret != "success":
+        return fail_test(test, cli_args, "%s failed" % " ".join(cli_args))
+
+    expected_file = os.path.join(
+        test_suite.get_output_directory(), test.get_font_name()
+    )
+    with open(expected_file, "rb") as fp:
+        expected_contents = fp.read()
+    with open(out_file, "rb") as fp:
+        actual_contents = fp.read()
+
+    if expected_contents == actual_contents:
+        if should_check_ots:
+            print("Checking output with ots-sanitize.")
+            if not check_ots(out_file):
+                return fail_test(test, cli_args, "ots for subsetted file fails.")
+        return 0
+
+    if TTFont is None:
+        print("fonttools is not present, skipping TTX diff.")
+        return fail_test(test, cli_args, "hash for expected and actual does not match.")
+
+    with io.StringIO() as fp:
+        try:
+            with TTFont(expected_file) as font:
+                font.saveXML(fp)
+        except Exception as e:
+            print(e)
+            return fail_test(test, cli_args, "ttx failed to parse the expected result")
+        expected_ttx = fp.getvalue()
+
+    with io.StringIO() as fp:
+        try:
+            with TTFont(out_file) as font:
+                font.saveXML(fp)
+        except Exception as e:
+            print(e)
+            return fail_test(test, cli_args, "ttx failed to parse the actual result")
+        actual_ttx = fp.getvalue()
+
+    if actual_ttx != expected_ttx:
+        for line in unified_diff(expected_ttx.splitlines(1), actual_ttx.splitlines(1)):
+            sys.stdout.write(line)
+        sys.stdout.flush()
+        return fail_test(test, cli_args, "ttx for expected and actual does not match.")
+
+    return fail_test(
+        test,
+        cli_args,
+        "hash for expected and actual does not match, "
+        "but the ttx matches. Expected file needs to be updated?",
+    )
+
+
+def has_ots():
+    if not ots_sanitize:
+        print("OTS is not present, skipping all ots checks.")
+        return False
+    return True
+
+
+def check_ots(path):
+    ots_report, returncode = cmd([ots_sanitize, path])
+    if returncode:
+        print("OTS Failure: %s" % ots_report)
+        return False
+    return True
+
+
+args = sys.argv[1:]
+if not args or sys.argv[1].find("hb-subset") == -1 or not os.path.exists(sys.argv[1]):
+    sys.exit("First argument does not seem to point to usable hb-subset.")
+hb_subset, args = args[0], args[1:]
+
+if not len(args):
+    sys.exit("No tests supplied.")
+
+has_ots = has_ots()
+
+env = os.environ.copy()
+env["LC_ALL"] = "C"
+
+EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER")
+
+
+def open_subset_batch_process():
+    cmd = [hb_subset, "--batch"]
+    if EXE_WRAPPER:
+        cmd = [EXE_WRAPPER] + cmd
+
+    process = subprocess.Popen(
+        cmd,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=sys.stdout,
+        env=env,
+    )
+    return process
+
+
+subset_process = open_subset_batch_process()
+out_dir = tempfile.mkdtemp()
+
+fails = 0
+for path in args:
+    with open(path, mode="r", encoding="utf-8") as f:
+        print("Running tests in " + path)
+        test_suite = SubsetTestSuite(path, f.read())
+        for test in test_suite.tests():
+            # Tests are run with and without preprocessing, results should be the
+            # same between them.
+            fails += run_test(test, has_ots, False)
+            fails += run_test(test, has_ots, True)
+
+if fails != 0:
+    sys.exit("%d test(s) failed; output left in %s" % (fails, out_dir))
+else:
+    print("All tests passed.")
+    shutil.rmtree(out_dir)