|
|
|
|
@ -4,6 +4,7 @@ import argparse
|
|
|
|
|
import concurrent.futures
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
@ -84,11 +85,11 @@ def make_binaries(test_dir: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_gdb(
|
|
|
|
|
gdb_binary: str, gdb_args: List[str], env=None, capture_output=True
|
|
|
|
|
gdb_path: str, gdb_args: List[str], env=None, capture_output=True
|
|
|
|
|
) -> CompletedProcess[str]:
|
|
|
|
|
env = os.environ if env is None else env
|
|
|
|
|
return subprocess.run(
|
|
|
|
|
[gdb_binary, "--silent", "--nx", "--nh"] + gdb_args + ["--eval-command", "quit"],
|
|
|
|
|
[gdb_path, "--silent", "--nx", "--nh"] + gdb_args + ["--eval-command", "quit"],
|
|
|
|
|
env=env,
|
|
|
|
|
capture_output=capture_output,
|
|
|
|
|
text=True,
|
|
|
|
|
@ -98,18 +99,20 @@ def run_gdb(
|
|
|
|
|
def get_tests_list(
|
|
|
|
|
collect_only: bool,
|
|
|
|
|
test_name_filter: str,
|
|
|
|
|
gdb_binary: str,
|
|
|
|
|
gdb_path: str,
|
|
|
|
|
gdbinit_path: str,
|
|
|
|
|
test_dir_path: str,
|
|
|
|
|
) -> List[str]:
|
|
|
|
|
# NOTE: We run tests under GDB sessions and because of some cleanup/tests dependencies problems
|
|
|
|
|
# we decided to run each test in a separate GDB session
|
|
|
|
|
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_collect.py"]
|
|
|
|
|
gdb_args = ["--command", "pytests_collect.py"]
|
|
|
|
|
if gdbinit_path:
|
|
|
|
|
gdb_args.extend(["--init-command", gdbinit_path])
|
|
|
|
|
|
|
|
|
|
env = os.environ.copy()
|
|
|
|
|
env["TESTS_PATH"] = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_dir_path)
|
|
|
|
|
|
|
|
|
|
result = run_gdb(gdb_binary, gdb_args, env=env)
|
|
|
|
|
result = run_gdb(gdb_path, gdb_args, env=env)
|
|
|
|
|
tests_collect_output = result.stdout
|
|
|
|
|
|
|
|
|
|
if result.returncode == 1:
|
|
|
|
|
@ -130,9 +133,12 @@ TEST_RETURN_TYPE = Tuple[CompletedProcess[str], str, float]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_test(
|
|
|
|
|
test_case: str, args: argparse.Namespace, gdb_binary: str, gdbinit_path: str, port: int = None
|
|
|
|
|
test_case: str, args: argparse.Namespace, gdb_path: str, gdbinit_path: str, port: int = None
|
|
|
|
|
) -> TEST_RETURN_TYPE:
|
|
|
|
|
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_launcher.py"]
|
|
|
|
|
gdb_args = ["--command", "pytests_launcher.py"]
|
|
|
|
|
if gdbinit_path:
|
|
|
|
|
gdb_args.extend(["--init-command", gdbinit_path])
|
|
|
|
|
|
|
|
|
|
if args.cov:
|
|
|
|
|
print("Running with coverage")
|
|
|
|
|
gdb_args = [
|
|
|
|
|
@ -154,7 +160,7 @@ def run_test(
|
|
|
|
|
env["QEMU_PORT"] = str(port)
|
|
|
|
|
|
|
|
|
|
started_at = time.time()
|
|
|
|
|
result = run_gdb(gdb_binary, gdb_args, env=env, capture_output=not args.serial)
|
|
|
|
|
result = run_gdb(gdb_path, gdb_args, env=env, capture_output=not args.serial)
|
|
|
|
|
duration = time.time() - started_at
|
|
|
|
|
return result, test_case, duration
|
|
|
|
|
|
|
|
|
|
@ -194,10 +200,9 @@ class TestStats:
|
|
|
|
|
def run_tests_and_print_stats(
|
|
|
|
|
tests_list: List[str],
|
|
|
|
|
args: argparse.Namespace,
|
|
|
|
|
gdb_binary: str,
|
|
|
|
|
gdb_path: str,
|
|
|
|
|
gdbinit_path: str,
|
|
|
|
|
test_dir_path: str,
|
|
|
|
|
ports: List[int] = [],
|
|
|
|
|
):
|
|
|
|
|
start = time.time()
|
|
|
|
|
test_results: List[TEST_RETURN_TYPE] = []
|
|
|
|
|
@ -205,7 +210,7 @@ def run_tests_and_print_stats(
|
|
|
|
|
|
|
|
|
|
if args.serial:
|
|
|
|
|
test_results = [
|
|
|
|
|
run_test(test, args, gdb_binary, gdbinit_path, reserve_port()) for test in tests_list
|
|
|
|
|
run_test(test, args, gdb_path, gdbinit_path, reserve_port()) for test in tests_list
|
|
|
|
|
]
|
|
|
|
|
else:
|
|
|
|
|
print("")
|
|
|
|
|
@ -213,7 +218,7 @@ def run_tests_and_print_stats(
|
|
|
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
|
|
|
|
for test in tests_list:
|
|
|
|
|
executor.submit(
|
|
|
|
|
run_test, test, args, gdb_binary, gdbinit_path, reserve_port()
|
|
|
|
|
run_test, test, args, gdb_path, gdbinit_path, reserve_port()
|
|
|
|
|
).add_done_callback(
|
|
|
|
|
lambda future: stats.handle_test_result(future.result(), args, test_dir_path)
|
|
|
|
|
)
|
|
|
|
|
@ -266,7 +271,7 @@ def parse_args():
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--nix",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="run tests using gdbinit.py built for nix environment",
|
|
|
|
|
help="run tests using built for nix environment",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--collect-only",
|
|
|
|
|
@ -292,31 +297,37 @@ def main():
|
|
|
|
|
if args.pdb:
|
|
|
|
|
print("Will run tests in serial and with Python debugger")
|
|
|
|
|
args.serial = True
|
|
|
|
|
|
|
|
|
|
if args.nix:
|
|
|
|
|
gdbinit_path = os.path.join(root_dir, "result/share/pwndbg/gdbinit.py")
|
|
|
|
|
if not os.path.exists(gdbinit_path):
|
|
|
|
|
print("ERROR: No nix-compatible gdbinit.py found. Run nix build .#pwndbg-dev")
|
|
|
|
|
gdbinit_path = ""
|
|
|
|
|
gdb_path = os.path.join(root_dir, "result/bin/pwndbg")
|
|
|
|
|
if not os.path.exists(gdb_path):
|
|
|
|
|
print("ERROR: No nix-compatible pwndbg found. Run nix build .#pwndbg-dev")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
os.environ["GDB_INIT_PATH"] = gdbinit_path
|
|
|
|
|
else:
|
|
|
|
|
gdbinit_path = os.path.join(root_dir, "gdbinit.py")
|
|
|
|
|
gdb_binary = "gdb"
|
|
|
|
|
if args.type == "cross-arch":
|
|
|
|
|
gdb_binary = "gdb-multiarch"
|
|
|
|
|
gdb_path = shutil.which(gdb_binary)
|
|
|
|
|
|
|
|
|
|
os.environ["GDB_INIT_PATH"] = gdbinit_path
|
|
|
|
|
os.environ["GDB_BIN_PATH"] = gdb_path
|
|
|
|
|
|
|
|
|
|
test_dir_path = TEST_FOLDER_NAME[args.type]
|
|
|
|
|
|
|
|
|
|
if args.type == "gdb":
|
|
|
|
|
gdb_binary = "gdb"
|
|
|
|
|
ensure_zig_path()
|
|
|
|
|
make_binaries(test_dir_path)
|
|
|
|
|
elif args.type == "cross-arch":
|
|
|
|
|
gdb_binary = "gdb-multiarch"
|
|
|
|
|
make_binaries(test_dir_path)
|
|
|
|
|
else:
|
|
|
|
|
raise NotImplementedError(args.type)
|
|
|
|
|
|
|
|
|
|
tests_list = get_tests_list(
|
|
|
|
|
args.collect_only, args.test_name_filter, gdb_binary, gdbinit_path, test_dir_path
|
|
|
|
|
args.collect_only, args.test_name_filter, gdb_path, gdbinit_path, test_dir_path
|
|
|
|
|
)
|
|
|
|
|
run_tests_and_print_stats(tests_list, args, gdb_binary, gdbinit_path, test_dir_path)
|
|
|
|
|
run_tests_and_print_stats(tests_list, args, gdb_path, gdbinit_path, test_dir_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|