mirror of https://github.com/pwndbg/pwndbg
Qemu user test structure (#2275)
* Initial version of qemu-user tests * Refactor testing files to reduce file duplication, introduce qemu-user-tests * lint and edit github actions workflow file. Move old qemu-user tests to seperate directory * Add iproute2 so ss command is available * test ubuntu 24 * funkiness with current working directory... * Further remote old test_qemu.sh and integrate into a Pytest fixture * lint * Disable ASLR, add test for aarch64 jumps * Use Popen.kill() function to make sure it closes. Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com> * qemu.kill() on the other fixture as well * comment * comment * lint * system test path stuff * remove old try-catch block * revert * revert path change * Use os._exit to pass return code, and move qemu-user tests above system tests because they run significantly faster * lint * Flush stdout before os._exit * Comment out flaky check for the address of main in old qemu tests * rename qemu-user to cross-arch * rename qemu-user to cross-arch and hotfix to not run pytest when cross-arch is used * remove todo comment * another comment * Test pwndbg.gdblib.symbol.address is not None and revert setarch -R * Revert os.exit change * Revert os.exit change * Revert os.exit change * readd os.exit in new exit places * lint * rebase * delete file introduced in rebase * break up tests into 3 files to invoke separately. Update GitHub workflow, remove code duplication in existing test * code coverage * fix code coverage * lint * test difference between Ubuntu 22 and 24 in Kernel tests * lint --------- Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com>
This commit is contained in:
parent
5954563a5d
commit
1438fc0616
|
@ -51,6 +51,7 @@ jobs:
|
||||||
mkdir .cov
|
mkdir .cov
|
||||||
sudo sysctl -w kernel.yama.ptrace_scope=0
|
sudo sysctl -w kernel.yama.ptrace_scope=0
|
||||||
./tests.sh --cov
|
./tests.sh --cov
|
||||||
|
./unit-tests.sh --cov
|
||||||
|
|
||||||
- name: Process coverage data
|
- name: Process coverage data
|
||||||
if: matrix.os == 'ubuntu-22.04'
|
if: matrix.os == 'ubuntu-22.04'
|
||||||
|
@ -62,6 +63,36 @@ jobs:
|
||||||
if: matrix.os == 'ubuntu-22.04'
|
if: matrix.os == 'ubuntu-22.04'
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
|
qemu-user-tests:
|
||||||
|
runs-on: [ubuntu-24.04]
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cache for pip
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ matrix.os }}-cache-pip
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
./setup.sh
|
||||||
|
./setup-dev.sh
|
||||||
|
mkdir .cov
|
||||||
|
|
||||||
|
- name: Run cross-architecture tests
|
||||||
|
run: |
|
||||||
|
./qemu-tests.sh --cov
|
||||||
|
|
||||||
|
- name: Process coverage data
|
||||||
|
run: |
|
||||||
|
./.venv/bin/coverage combine
|
||||||
|
./.venv/bin/coverage xml
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
qemu-tests:
|
qemu-tests:
|
||||||
runs-on: [ubuntu-22.04]
|
runs-on: [ubuntu-22.04]
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
@ -79,7 +110,7 @@ jobs:
|
||||||
./setup.sh
|
./setup.sh
|
||||||
./setup-dev.sh
|
./setup-dev.sh
|
||||||
mkdir .cov
|
mkdir .cov
|
||||||
|
|
||||||
- name: Set up cache for QEMU images
|
- name: Set up cache for QEMU images
|
||||||
id: qemu-cache
|
id: qemu-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
@ -98,11 +129,6 @@ jobs:
|
||||||
sudo sysctl -w kernel.yama.ptrace_scope=0
|
sudo sysctl -w kernel.yama.ptrace_scope=0
|
||||||
./tests.sh --cov
|
./tests.sh --cov
|
||||||
|
|
||||||
- name: Run qemu-user tests
|
|
||||||
working-directory: ./tests/qemu-tests
|
|
||||||
run: |
|
|
||||||
./test_qemu.sh
|
|
||||||
|
|
||||||
- name: Process coverage data
|
- name: Process coverage data
|
||||||
run: |
|
run: |
|
||||||
./.venv/bin/coverage combine
|
./.venv/bin/coverage combine
|
||||||
|
|
|
@ -27,6 +27,15 @@ services:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
image: ubuntu:22.04
|
image: ubuntu:22.04
|
||||||
|
|
||||||
|
ubuntu24.04:
|
||||||
|
<<: *base-spec
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
image: ubuntu:24.04
|
||||||
|
|
||||||
|
|
||||||
debian11:
|
debian11:
|
||||||
<<: *base-spec
|
<<: *base-spec
|
||||||
|
|
|
@ -332,6 +332,36 @@ def can_run_first_emulate() -> bool:
|
||||||
first_time_emulate = True
|
first_time_emulate = True
|
||||||
|
|
||||||
|
|
||||||
|
def no_emulate_one():
|
||||||
|
result = near(pwndbg.gdblib.regs.pc, emulate=False, show_prev_insns=False)
|
||||||
|
if result:
|
||||||
|
return result[0][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def emulate_one():
|
||||||
|
result = near(pwndbg.gdblib.regs.pc, emulate=True, show_prev_insns=False)
|
||||||
|
if result:
|
||||||
|
return result[0][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def one_with_config():
|
||||||
|
"""
|
||||||
|
Returns a single Pwndbg Instruction at the current PC.
|
||||||
|
|
||||||
|
Emulation determined by the `pwndbg.config.emulate` setting.
|
||||||
|
"""
|
||||||
|
result = near(
|
||||||
|
pwndbg.gdblib.regs.pc,
|
||||||
|
emulate=bool(not pwndbg.config.emulate == "off"),
|
||||||
|
show_prev_insns=False,
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
return result[0][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Return (list of PwndbgInstructions, index in list where instruction.address = passed in address)
|
# Return (list of PwndbgInstructions, index in list where instruction.address = passed in address)
|
||||||
def near(
|
def near(
|
||||||
address, instructions=1, emulate=False, show_prev_insns=True, use_cache=False, linear=False
|
address, instructions=1, emulate=False, show_prev_insns=True, use_cache=False, linear=False
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
(cd tests && python3 tests.py -t cross-arch $@)
|
||||||
|
exit_code=$?
|
||||||
|
exit $exit_code
|
|
@ -97,11 +97,15 @@ install_apt() {
|
||||||
gdb-multiarch \
|
gdb-multiarch \
|
||||||
parallel \
|
parallel \
|
||||||
netcat-openbsd \
|
netcat-openbsd \
|
||||||
|
iproute2 \
|
||||||
qemu-system-x86 \
|
qemu-system-x86 \
|
||||||
qemu-system-arm \
|
qemu-system-arm \
|
||||||
qemu-user \
|
qemu-user \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
gcc-riscv64-linux-gnu
|
gcc-riscv64-linux-gnu \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
gcc-mips-linux-gnu \
|
||||||
|
gcc-mips64-linux-gnuabi64
|
||||||
|
|
||||||
if [[ "$1" != "" && "$1" != "20.04" ]]; then
|
if [[ "$1" != "" && "$1" != "20.04" ]]; then
|
||||||
sudo apt install shfmt
|
sudo apt install shfmt
|
||||||
|
|
20
tests.sh
20
tests.sh
|
@ -1,24 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Run integration tests
|
# Run integration tests
|
||||||
(cd tests/gdb-tests && python3 tests.py $@)
|
(cd tests && python3 tests.py $@)
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
|
|
||||||
COV=0
|
|
||||||
# Run unit tests
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [ "$arg" == "--cov" ]; then
|
|
||||||
COV=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $COV -eq 1 ]; then
|
|
||||||
coverage run -m pytest tests/unit-tests
|
|
||||||
else
|
|
||||||
pytest tests/unit-tests
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit_code=$((exit_code + $?))
|
|
||||||
|
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
|
|
|
@ -5,7 +5,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
gdb_init_path = os.environ.get("GDB_INIT_PATH", "../../gdbinit.py")
|
gdb_init_path = os.environ.get("GDB_INIT_PATH", "../gdbinit.py")
|
||||||
|
|
||||||
|
|
||||||
def run_gdb_with_script(
|
def run_gdb_with_script(
|
||||||
|
|
|
@ -5,7 +5,12 @@ import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
TESTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "tests")
|
TESTS_PATH = os.environ.get("TESTS_PATH")
|
||||||
|
|
||||||
|
if TESTS_PATH is None:
|
||||||
|
print("'TESTS_PATH' environment variable not set. Failed to collect tests.")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
|
||||||
class CollectTestFunctionNames:
|
class CollectTestFunctionNames:
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import coverage
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
use_pdb = os.environ.get("USE_PDB") == "1"
|
use_pdb = os.environ.get("USE_PDB") == "1"
|
||||||
|
@ -29,4 +30,8 @@ if return_code != 0:
|
||||||
print("If you want to debug tests locally, run ./tests.sh with the --pdb flag")
|
print("If you want to debug tests locally, run ./tests.sh with the --pdb flag")
|
||||||
print("-" * 80)
|
print("-" * 80)
|
||||||
|
|
||||||
sys.exit(return_code)
|
# We must call this to ensure the code coverage file writes get flushed
|
||||||
|
# https://github.com/nedbat/coveragepy/issues/310
|
||||||
|
coverage._atexit()
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(return_code)
|
|
@ -1,15 +0,0 @@
|
||||||
.PHONY: all
|
|
||||||
all: reference-binary.aarch64.out reference-binary.riscv64.out
|
|
||||||
|
|
||||||
reference-binary.aarch64.out : reference-binary.aarch64.c
|
|
||||||
@echo "[+] Building '$@'"
|
|
||||||
@aarch64-linux-gnu-gcc $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $? $(LDFLAGS)
|
|
||||||
|
|
||||||
# apt install crossbuild-essential-riscv64
|
|
||||||
reference-binary.riscv64.out : reference-binary.riscv64.c
|
|
||||||
@echo "[+] Building '$@'"
|
|
||||||
@riscv64-linux-gnu-gcc -march=rv64gc -mabi=lp64d -g $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $? $(LDFLAGS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm reference-binary.aarch64.out
|
|
||||||
rm reference-binary.riscv64.out
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
"""
|
||||||
|
This file should consist of global test fixtures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gdb
|
||||||
|
import pytest
|
||||||
|
from pwn import context
|
||||||
|
from pwn import make_elf_from_assembly
|
||||||
|
|
||||||
|
_start_binary_called = False
|
||||||
|
|
||||||
|
QEMU_PORT = os.environ.get("QEMU_PORT")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def qemu_assembly_run():
|
||||||
|
"""
|
||||||
|
Returns function that launches given binary with 'starti' command
|
||||||
|
|
||||||
|
The `path` is returned from `make_elf_from_assembly` (provided by pwntools)
|
||||||
|
"""
|
||||||
|
|
||||||
|
qemu: subprocess.Popen = None
|
||||||
|
|
||||||
|
if QEMU_PORT is None:
|
||||||
|
print("'QEMU_PORT' environment variable not set")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
def _start_binary(asm: str, arch: str, *args):
|
||||||
|
nonlocal qemu
|
||||||
|
|
||||||
|
context.arch = arch
|
||||||
|
binary_tmp_path = make_elf_from_assembly(asm)
|
||||||
|
|
||||||
|
qemu = subprocess.Popen(
|
||||||
|
[
|
||||||
|
f"qemu-{arch}",
|
||||||
|
"-g",
|
||||||
|
f"{QEMU_PORT}",
|
||||||
|
f"{binary_tmp_path}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
gdb.execute(f"target remote :{QEMU_PORT}")
|
||||||
|
gdb.execute("set exception-verbose on")
|
||||||
|
|
||||||
|
global _start_binary_called
|
||||||
|
# if _start_binary_called:
|
||||||
|
# raise Exception('Starting more than one binary is not supported in pwndbg tests.')
|
||||||
|
|
||||||
|
_start_binary_called = True
|
||||||
|
|
||||||
|
yield _start_binary
|
||||||
|
|
||||||
|
qemu.kill()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def qemu_start_binary():
|
||||||
|
"""
|
||||||
|
Returns function that launches given binary with 'starti' command
|
||||||
|
|
||||||
|
Argument `path` is the path to the binary
|
||||||
|
"""
|
||||||
|
|
||||||
|
qemu: subprocess.Popen = None
|
||||||
|
|
||||||
|
if QEMU_PORT is None:
|
||||||
|
print("'QEMU_PORT' environment variable not set")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
def _start_binary(path: str, arch: str, *args):
|
||||||
|
nonlocal qemu
|
||||||
|
|
||||||
|
qemu = subprocess.Popen(
|
||||||
|
[
|
||||||
|
f"qemu-{arch}",
|
||||||
|
"-L",
|
||||||
|
f"/usr/{arch}-linux-gnu/",
|
||||||
|
"-g",
|
||||||
|
f"{QEMU_PORT}",
|
||||||
|
f"{path}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
gdb.execute(f"target remote :{QEMU_PORT}")
|
||||||
|
gdb.execute("set exception-verbose on")
|
||||||
|
|
||||||
|
global _start_binary_called
|
||||||
|
# if _start_binary_called:
|
||||||
|
# raise Exception('Starting more than one binary is not supported in pwndbg tests.')
|
||||||
|
|
||||||
|
_start_binary_called = True
|
||||||
|
|
||||||
|
yield _start_binary
|
||||||
|
|
||||||
|
qemu.kill()
|
|
@ -1,36 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
TESTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "tests/system")
|
|
||||||
|
|
||||||
|
|
||||||
class CollectTestFunctionNames:
|
|
||||||
"""See https://github.com/pytest-dev/pytest/issues/2039#issuecomment-257753269"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.collected = []
|
|
||||||
|
|
||||||
def pytest_collection_modifyitems(self, items):
|
|
||||||
for item in items:
|
|
||||||
self.collected.append(item.nodeid)
|
|
||||||
|
|
||||||
|
|
||||||
collector = CollectTestFunctionNames()
|
|
||||||
rv = pytest.main(["--collect-only", TESTS_PATH], plugins=[collector])
|
|
||||||
|
|
||||||
if rv == pytest.ExitCode.INTERRUPTED:
|
|
||||||
print("Failed to collect all tests, perhaps there is a syntax error in one of test files?")
|
|
||||||
sys.stdout.flush()
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
print("Listing collected tests:")
|
|
||||||
for nodeid in collector.collected:
|
|
||||||
print("Test:", nodeid)
|
|
||||||
|
|
||||||
# easy way to exit GDB session
|
|
||||||
sys.exit(0)
|
|
|
@ -1,32 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
use_pdb = os.environ.get("USE_PDB") == "1"
|
|
||||||
|
|
||||||
sys._pwndbg_unittest_run = True
|
|
||||||
|
|
||||||
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
test = os.environ["PWNDBG_LAUNCH_TEST"]
|
|
||||||
|
|
||||||
test = os.path.join(CURRENT_DIR, test)
|
|
||||||
|
|
||||||
args = [test, "-vvv", "-s", "--showlocals", "--color=yes"]
|
|
||||||
|
|
||||||
if use_pdb:
|
|
||||||
args.append("--pdb")
|
|
||||||
|
|
||||||
print(f"Launching pytest with args: {args}")
|
|
||||||
|
|
||||||
return_code = pytest.main(args)
|
|
||||||
|
|
||||||
if return_code != 0:
|
|
||||||
print("-" * 80)
|
|
||||||
print("If you want to debug tests locally, run ./tests.sh with the --pdb flag")
|
|
||||||
print("-" * 80)
|
|
||||||
|
|
||||||
sys.exit(return_code)
|
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
make -C binaries
|
|
||||||
|
|
||||||
ROOT_DIR="$(readlink -f ../../)"
|
|
||||||
GDB_INIT_PATH="$ROOT_DIR/gdbinit.py"
|
|
||||||
COVERAGERC_PATH="$ROOT_DIR/pyproject.toml"
|
|
||||||
|
|
||||||
handle_sigint() {
|
|
||||||
echo "Exiting..." >&2
|
|
||||||
pkill qemu-aarch64
|
|
||||||
pkill qemu-riscv64
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
trap handle_sigint SIGINT
|
|
||||||
|
|
||||||
gdb_load_pwndbg=(--command "$GDB_INIT_PATH" -ex "set exception-verbose on")
|
|
||||||
run_gdb() {
|
|
||||||
COVERAGE_FILE=$ROOT_DIR/.cov/coverage \
|
|
||||||
COVERAGE_PROCESS_START=$COVERAGERC_PATH \
|
|
||||||
PWNDBG_DISABLE_COLORS=1 \
|
|
||||||
gdb-multiarch --silent --nx --nh "${gdb_load_pwndbg[@]}" "$@" -ex "quit" 2> /dev/null
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
test_arch() {
|
|
||||||
local arch="$1"
|
|
||||||
|
|
||||||
qemu-${arch} \
|
|
||||||
-g 1234 \
|
|
||||||
-L /usr/${arch}-linux-gnu/ \
|
|
||||||
./binaries/reference-binary.${arch}.out &
|
|
||||||
|
|
||||||
run_gdb \
|
|
||||||
-ex "set sysroot /usr/${arch}-linux-gnu/" \
|
|
||||||
-ex "file ./binaries/reference-binary.${arch}.out" \
|
|
||||||
-ex 'py import coverage;coverage.process_startup()' \
|
|
||||||
-ex "target remote :1234" \
|
|
||||||
-ex "source ./tests/user/test_${arch}.py"
|
|
||||||
local result=$?
|
|
||||||
pkill qemu-${arch}
|
|
||||||
return $result
|
|
||||||
}
|
|
||||||
|
|
||||||
ARCHS=("aarch64" "riscv64")
|
|
||||||
|
|
||||||
FAILED_TESTS=()
|
|
||||||
for arch in "${ARCHS[@]}"; do
|
|
||||||
test_arch "$arch"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
FAILED_TESTS+=("$arch")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "${#FAILED_TESTS[@]}" -ne 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "Failing tests: ${FAILED_TESTS[@]}"
|
|
||||||
echo ""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -123,8 +123,8 @@ run_gdb() {
|
||||||
|
|
||||||
# NOTE: We run tests under GDB sessions and because of some cleanup/tests dependencies problems
|
# 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
|
# we decided to run each test in a separate GDB session
|
||||||
gdb_args=(--command pytests_collect.py)
|
gdb_args=(--command ../pytests_collect.py)
|
||||||
TESTS_COLLECT_OUTPUT=$(run_gdb "x86_64" "${gdb_args[@]}")
|
TESTS_COLLECT_OUTPUT=$(TESTS_PATH="$ROOT_DIR/tests/qemu-tests/tests/system" run_gdb "x86_64" "${gdb_args[@]}")
|
||||||
|
|
||||||
if [ $? -eq 1 ]; then
|
if [ $? -eq 1 ]; then
|
||||||
echo -E "$TESTS_COLLECT_OUTPUT"
|
echo -E "$TESTS_COLLECT_OUTPUT"
|
||||||
|
@ -155,7 +155,7 @@ run_test() {
|
||||||
local arch="$4"
|
local arch="$4"
|
||||||
|
|
||||||
gdb_connect_qemu=(-ex "file ${IMAGE_DIR}/vmlinux-${kernel_type}-${kernel_version}-${arch}" -ex "target remote :${GDB_PORT}")
|
gdb_connect_qemu=(-ex "file ${IMAGE_DIR}/vmlinux-${kernel_type}-${kernel_version}-${arch}" -ex "target remote :${GDB_PORT}")
|
||||||
gdb_args=("${gdb_connect_qemu[@]}" --command pytests_launcher.py)
|
gdb_args=("${gdb_connect_qemu[@]}" --command ../pytests_launcher.py)
|
||||||
if [ ${RUN_CODECOV} -ne 0 ]; then
|
if [ ${RUN_CODECOV} -ne 0 ]; then
|
||||||
gdb_args=(-ex 'py import coverage;coverage.process_startup()' "${gdb_args[@]}")
|
gdb_args=(-ex 'py import coverage;coverage.process_startup()' "${gdb_args[@]}")
|
||||||
fi
|
fi
|
||||||
|
@ -164,7 +164,7 @@ run_test() {
|
||||||
COVERAGE_FILE=$ROOT_DIR/.cov/coverage \
|
COVERAGE_FILE=$ROOT_DIR/.cov/coverage \
|
||||||
COVERAGE_PROCESS_START=$COVERAGERC_PATH \
|
COVERAGE_PROCESS_START=$COVERAGERC_PATH \
|
||||||
USE_PDB="${USE_PDB}" \
|
USE_PDB="${USE_PDB}" \
|
||||||
PWNDBG_LAUNCH_TEST="${test_case}" \
|
PWNDBG_LAUNCH_TEST="qemu-tests/${test_case}" \
|
||||||
PWNDBG_DISABLE_COLORS=1 \
|
PWNDBG_DISABLE_COLORS=1 \
|
||||||
PWNDBG_ARCH="${arch}" \
|
PWNDBG_ARCH="${arch}" \
|
||||||
PWNDBG_KERNEL_TYPE="${kernel_type}" \
|
PWNDBG_KERNEL_TYPE="${kernel_type}" \
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from . import binaries
|
|
@ -0,0 +1,19 @@
|
||||||
|
.PHONY: all
|
||||||
|
all: reference-binary.aarch64.out reference-binary.riscv64.out
|
||||||
|
|
||||||
|
%.aarch64.out : %.aarch64.c
|
||||||
|
@echo "[+] Building '$@'"
|
||||||
|
@aarch64-linux-gnu-gcc $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $< $(LDFLAGS)
|
||||||
|
|
||||||
|
%.riscv64.out : %.riscv64.c
|
||||||
|
@echo "[+] Building '$@'"
|
||||||
|
@riscv64-linux-gnu-gcc -march=rv64gc -mabi=lp64d -g $(CFLAGS) $(EXTRA_FLAGS) -w -o $@ $? $(LDFLAGS)
|
||||||
|
|
||||||
|
AARCH64_SOURCES := $(wildcard *.aarch64.c)
|
||||||
|
AARCH64_TARGETS := $(AARCH64_SOURCES:.aarch64.c=.aarch64.out)
|
||||||
|
|
||||||
|
RISCV64_SOURCES := $(wildcard *.riscv64.c)
|
||||||
|
RISCV64_TARGETS := $(RISCV64_SOURCES:.riscv64.c=.riscv64.out)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.aarch64.out *.x86_64.out *.arm.out
|
|
@ -0,0 +1,9 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
path = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def get(x):
|
||||||
|
return os.path.join(path, x)
|
|
@ -0,0 +1,136 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import gdb
|
||||||
|
from capstone.arm64_const import ARM64_INS_BL
|
||||||
|
|
||||||
|
import pwndbg.gdblib.disasm
|
||||||
|
import pwndbg.gdblib.nearpc
|
||||||
|
from pwndbg.gdblib.disasm.instruction import InstructionCondition
|
||||||
|
|
||||||
|
AARCH64_GRACEFUL_EXIT = """
|
||||||
|
mov x0, 0
|
||||||
|
mov x8, 93
|
||||||
|
svc 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
SIMPLE_FUNCTION = f"""
|
||||||
|
|
||||||
|
bl my_function
|
||||||
|
b end
|
||||||
|
|
||||||
|
my_function:
|
||||||
|
ret
|
||||||
|
|
||||||
|
end:
|
||||||
|
{AARCH64_GRACEFUL_EXIT}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_syscall_annotation(qemu_assembly_run):
|
||||||
|
""" """
|
||||||
|
qemu_assembly_run(AARCH64_GRACEFUL_EXIT, "aarch64")
|
||||||
|
|
||||||
|
instructions = pwndbg.gdblib.disasm.near(
|
||||||
|
address=pwndbg.gdblib.regs.pc, instructions=3, emulate=True
|
||||||
|
)[0]
|
||||||
|
future_syscall_ins = instructions[2]
|
||||||
|
|
||||||
|
assert future_syscall_ins.syscall == 93
|
||||||
|
assert future_syscall_ins.syscall_name == "exit"
|
||||||
|
|
||||||
|
gdb.execute("stepuntilasm svc")
|
||||||
|
|
||||||
|
# Both for emulation and non-emulation, ensure a syscall at current PC gets enriched
|
||||||
|
instructions = pwndbg.gdblib.disasm.emulate_one(), pwndbg.gdblib.disasm.no_emulate_one()
|
||||||
|
|
||||||
|
for i in instructions:
|
||||||
|
assert i.syscall == 93
|
||||||
|
assert i.syscall_name == "exit"
|
||||||
|
|
||||||
|
|
||||||
|
def test_branch_enhancement(qemu_assembly_run):
|
||||||
|
qemu_assembly_run(SIMPLE_FUNCTION, "aarch64")
|
||||||
|
|
||||||
|
instruction = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert instruction.id == ARM64_INS_BL
|
||||||
|
assert instruction.call_like
|
||||||
|
assert not instruction.is_conditional_jump
|
||||||
|
assert instruction.is_unconditional_jump
|
||||||
|
assert instruction.target_string == "my_function"
|
||||||
|
|
||||||
|
|
||||||
|
CONDITIONAL_JUMPS = f"""
|
||||||
|
mov x2, 0b1010
|
||||||
|
mov x3, 0
|
||||||
|
|
||||||
|
cbz x3, A
|
||||||
|
nop
|
||||||
|
|
||||||
|
A:
|
||||||
|
cbnz x2, B
|
||||||
|
nop
|
||||||
|
|
||||||
|
B:
|
||||||
|
tbz x2, #0, C
|
||||||
|
nop
|
||||||
|
|
||||||
|
C:
|
||||||
|
tbnz x2, #3, D
|
||||||
|
nop
|
||||||
|
|
||||||
|
D:
|
||||||
|
cmp x2, x3
|
||||||
|
b.eq E
|
||||||
|
nop
|
||||||
|
|
||||||
|
E:
|
||||||
|
b.ne F
|
||||||
|
nop
|
||||||
|
|
||||||
|
F:
|
||||||
|
{AARCH64_GRACEFUL_EXIT}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_conditional_jumps(qemu_assembly_run):
|
||||||
|
qemu_assembly_run(CONDITIONAL_JUMPS, "aarch64")
|
||||||
|
|
||||||
|
gdb.execute("stepuntilasm cbz")
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.TRUE
|
||||||
|
|
||||||
|
gdb.execute("si")
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.TRUE
|
||||||
|
|
||||||
|
gdb.execute("si")
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.TRUE
|
||||||
|
|
||||||
|
gdb.execute("si")
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.TRUE
|
||||||
|
|
||||||
|
gdb.execute("si")
|
||||||
|
gdb.execute("si")
|
||||||
|
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.FALSE
|
||||||
|
|
||||||
|
gdb.execute("si")
|
||||||
|
gdb.execute("si")
|
||||||
|
|
||||||
|
ins = pwndbg.gdblib.disasm.one_with_config()
|
||||||
|
|
||||||
|
assert ins.condition == InstructionCondition.TRUE
|
||||||
|
|
||||||
|
|
||||||
|
def test_conditional_jumps_no_emulate(qemu_assembly_run):
|
||||||
|
gdb.execute("set emulate off")
|
||||||
|
test_conditional_jumps(qemu_assembly_run)
|
|
@ -1,15 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import gdb
|
import gdb
|
||||||
|
import user
|
||||||
|
|
||||||
import pwndbg
|
import pwndbg.gdblib.symbol
|
||||||
|
|
||||||
try:
|
REFERENCE_BINARY = user.binaries.get("reference-binary.aarch64.out")
|
||||||
|
|
||||||
|
|
||||||
|
def test_aarch64_reference(qemu_start_binary):
|
||||||
|
qemu_start_binary(REFERENCE_BINARY, "aarch64")
|
||||||
gdb.execute("break break_here")
|
gdb.execute("break break_here")
|
||||||
assert pwndbg.gdblib.symbol.address("main") == 0x5500000A1C
|
assert pwndbg.gdblib.symbol.address("main") is not None
|
||||||
gdb.execute("continue")
|
gdb.execute("continue")
|
||||||
|
|
||||||
gdb.execute("argv", to_string=True)
|
gdb.execute("argv", to_string=True)
|
||||||
|
@ -35,6 +37,3 @@ try:
|
||||||
gdb.execute("piebase", to_string=True)
|
gdb.execute("piebase", to_string=True)
|
||||||
|
|
||||||
gdb.execute("nextret", to_string=True)
|
gdb.execute("nextret", to_string=True)
|
||||||
except AssertionError:
|
|
||||||
traceback.print_exc(file=sys.stdout)
|
|
||||||
sys.exit(1)
|
|
|
@ -1,15 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import gdb
|
import gdb
|
||||||
|
import user
|
||||||
|
|
||||||
import pwndbg
|
import pwndbg.gdblib.symbol
|
||||||
|
|
||||||
try:
|
REFERENCE_BINARY = user.binaries.get("reference-binary.riscv64.out")
|
||||||
|
|
||||||
|
|
||||||
|
def test_riscv64_reference(qemu_start_binary):
|
||||||
|
qemu_start_binary(REFERENCE_BINARY, "riscv64")
|
||||||
gdb.execute("break 4")
|
gdb.execute("break 4")
|
||||||
assert pwndbg.gdblib.symbol.address("main") == 0x4000000668
|
assert pwndbg.gdblib.symbol.address("main") is not None
|
||||||
gdb.execute("continue")
|
gdb.execute("continue")
|
||||||
|
|
||||||
gdb.execute("stepuntilasm jalr")
|
gdb.execute("stepuntilasm jalr")
|
||||||
|
@ -26,7 +28,3 @@ try:
|
||||||
gdb.execute("stepi")
|
gdb.execute("stepi")
|
||||||
assembly = gdb.execute("nearpc 0", to_string=True)
|
assembly = gdb.execute("nearpc 0", to_string=True)
|
||||||
assert assembly.split()[2] == target, (assembly.split()[2], target)
|
assert assembly.split()[2] == target, (assembly.split()[2], target)
|
||||||
|
|
||||||
except AssertionError:
|
|
||||||
traceback.print_exc(file=sys.stdout)
|
|
||||||
sys.exit(1)
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -11,7 +12,7 @@ from subprocess import CompletedProcess
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
root_dir = os.path.realpath("../../")
|
root_dir = os.path.realpath("../")
|
||||||
|
|
||||||
|
|
||||||
def ensureZigPath():
|
def ensureZigPath():
|
||||||
|
@ -24,27 +25,66 @@ def ensureZigPath():
|
||||||
|
|
||||||
def makeBinaries():
|
def makeBinaries():
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["make", "all"], cwd="./tests/binaries")
|
subprocess.check_call(["make", "all"], cwd="./gdb-tests/tests/binaries")
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def run_gdb(gdb_args: List[str], env=None, capture_output=True) -> CompletedProcess[str]:
|
def makeCrossArchBinaries():
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["make", "all"], cwd="./qemu-tests/tests/user/binaries")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def open_ports(n: int) -> List[int]:
|
||||||
|
"""
|
||||||
|
Returns a list of `n` open ports
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["netstat", "-tuln"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
# If netstat not found, try ss
|
||||||
|
raise FileNotFoundError
|
||||||
|
except FileNotFoundError:
|
||||||
|
result = subprocess.run(["ss", "-tuln"], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
used_ports = set(re.findall(r":(\d+)", result.stdout.decode()))
|
||||||
|
used_ports = set(map(int, used_ports))
|
||||||
|
|
||||||
|
available_ports = [port for port in range(1024, 65536) if port not in used_ports]
|
||||||
|
return random.sample(available_ports, n)
|
||||||
|
|
||||||
|
|
||||||
|
def run_gdb(
|
||||||
|
gdb_binary: str, gdb_args: List[str], env=None, capture_output=True
|
||||||
|
) -> CompletedProcess[str]:
|
||||||
env = os.environ if env is None else env
|
env = os.environ if env is None else env
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
["gdb", "--silent", "--nx", "--nh"] + gdb_args + ["--eval-command", "quit"],
|
[gdb_binary, "--silent", "--nx", "--nh"] + gdb_args + ["--eval-command", "quit"],
|
||||||
env=env,
|
env=env,
|
||||||
capture_output=capture_output,
|
capture_output=capture_output,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def getTestsList(collect_only: bool, test_name_filter: str, gdbinit_path: str) -> List[str]:
|
def getTestsList(
|
||||||
|
collect_only: bool,
|
||||||
|
test_name_filter: str,
|
||||||
|
gdb_binary: 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
|
# 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
|
# we decided to run each test in a separate GDB session
|
||||||
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_collect.py"]
|
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_collect.py"]
|
||||||
|
|
||||||
result = run_gdb(gdb_args)
|
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)
|
||||||
tests_collect_output = result.stdout
|
tests_collect_output = result.stdout
|
||||||
|
|
||||||
if result.returncode == 1:
|
if result.returncode == 1:
|
||||||
|
@ -55,14 +95,14 @@ def getTestsList(collect_only: bool, test_name_filter: str, gdbinit_path: str) -
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
# Extract the test names from the output using regex
|
# Extract the test names from the output using regex
|
||||||
pattern = re.compile(r"tests/.*::.*")
|
pattern = re.compile(rf"{test_dir_path}.*::.*")
|
||||||
matches = pattern.findall(tests_collect_output)
|
matches = pattern.findall(tests_collect_output)
|
||||||
tests_list = [match for match in matches if re.search(test_name_filter, match)]
|
tests_list = [match for match in matches if re.search(test_name_filter, match)]
|
||||||
return tests_list
|
return tests_list
|
||||||
|
|
||||||
|
|
||||||
def run_test(
|
def run_test(
|
||||||
test_case: str, args: argparse.Namespace, gdbinit_path: str
|
test_case: str, args: argparse.Namespace, gdb_binary: str, gdbinit_path: str, port: int = None
|
||||||
) -> Tuple[CompletedProcess[str], str]:
|
) -> Tuple[CompletedProcess[str], str]:
|
||||||
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_launcher.py"]
|
gdb_args = ["--init-command", gdbinit_path, "--command", "pytests_launcher.py"]
|
||||||
if args.cov:
|
if args.cov:
|
||||||
|
@ -82,11 +122,20 @@ def run_test(
|
||||||
env["USE_PDB"] = "1"
|
env["USE_PDB"] = "1"
|
||||||
env["PWNDBG_LAUNCH_TEST"] = test_case
|
env["PWNDBG_LAUNCH_TEST"] = test_case
|
||||||
env["PWNDBG_DISABLE_COLORS"] = "1"
|
env["PWNDBG_DISABLE_COLORS"] = "1"
|
||||||
result = run_gdb(gdb_args, env=env, capture_output=not args.serial)
|
if port is not None:
|
||||||
|
env["QEMU_PORT"] = str(port)
|
||||||
|
result = run_gdb(gdb_binary, gdb_args, env=env, capture_output=not args.serial)
|
||||||
return (result, test_case)
|
return (result, test_case)
|
||||||
|
|
||||||
|
|
||||||
def run_tests_and_print_stats(tests_list: List[str], args: argparse.Namespace, gdbinit_path: str):
|
def run_tests_and_print_stats(
|
||||||
|
tests_list: List[str],
|
||||||
|
args: argparse.Namespace,
|
||||||
|
gdb_binary: str,
|
||||||
|
gdbinit_path: str,
|
||||||
|
test_dir_path: str,
|
||||||
|
ports: List[int] = [],
|
||||||
|
):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
test_results: List[Tuple[CompletedProcess[str], str]] = []
|
test_results: List[Tuple[CompletedProcess[str], str]] = []
|
||||||
|
|
||||||
|
@ -96,7 +145,7 @@ def run_tests_and_print_stats(tests_list: List[str], args: argparse.Namespace, g
|
||||||
content = process.stdout
|
content = process.stdout
|
||||||
|
|
||||||
# Extract the test name and result using regex
|
# Extract the test name and result using regex
|
||||||
testname = re.search(r"^(tests/[^ ]+)", content, re.MULTILINE)[0]
|
testname = re.search(rf"^({test_dir_path}/[^ ]+)", content, re.MULTILINE)[0]
|
||||||
result = re.search(
|
result = re.search(
|
||||||
r"(\x1b\[3.m(PASSED|FAILED|SKIPPED|XPASS|XFAIL)\x1b\[0m)", content, re.MULTILINE
|
r"(\x1b\[3.m(PASSED|FAILED|SKIPPED|XPASS|XFAIL)\x1b\[0m)", content, re.MULTILINE
|
||||||
)[0]
|
)[0]
|
||||||
|
@ -109,16 +158,21 @@ def run_tests_and_print_stats(tests_list: List[str], args: argparse.Namespace, g
|
||||||
print("")
|
print("")
|
||||||
print(content)
|
print(content)
|
||||||
|
|
||||||
|
port_iterator = iter(ports)
|
||||||
|
|
||||||
if args.serial:
|
if args.serial:
|
||||||
test_results = [run_test(test, args, gdbinit_path) for test in tests_list]
|
test_results = [
|
||||||
|
run_test(test, args, gdb_binary, gdbinit_path, next(port_iterator, None))
|
||||||
|
for test in tests_list
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
print("")
|
print("")
|
||||||
print("Running tests in parallel")
|
print("Running tests in parallel")
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||||
for test in tests_list:
|
for test in tests_list:
|
||||||
executor.submit(run_test, test, args, gdbinit_path).add_done_callback(
|
executor.submit(
|
||||||
lambda future: handle_parallel_test_result(future.result())
|
run_test, test, args, gdb_binary, gdbinit_path, next(port_iterator, None)
|
||||||
)
|
).add_done_callback(lambda future: handle_parallel_test_result(future.result()))
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
seconds = int(end - start)
|
seconds = int(end - start)
|
||||||
|
@ -145,6 +199,8 @@ def run_tests_and_print_stats(tests_list: List[str], args: argparse.Namespace, g
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description="Run tests.")
|
parser = argparse.ArgumentParser(description="Run tests.")
|
||||||
|
parser.add_argument("-t", "--type", dest="type", choices=["gdb", "cross-arch"], default="gdb")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-p",
|
"-p",
|
||||||
"--pdb",
|
"--pdb",
|
||||||
|
@ -177,6 +233,8 @@ def parse_args():
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
TEST_FOLDER_NAME = {"gdb": "gdb-tests/tests", "cross-arch": "qemu-tests/tests/user"}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
if args.cov:
|
if args.cov:
|
||||||
|
@ -192,7 +250,24 @@ if __name__ == "__main__":
|
||||||
os.environ["GDB_INIT_PATH"] = gdbinit_path
|
os.environ["GDB_INIT_PATH"] = gdbinit_path
|
||||||
else:
|
else:
|
||||||
gdbinit_path = os.path.join(root_dir, "gdbinit.py")
|
gdbinit_path = os.path.join(root_dir, "gdbinit.py")
|
||||||
ensureZigPath()
|
|
||||||
makeBinaries()
|
gdb_binary = "gdb"
|
||||||
tests: List[str] = getTestsList(args.collect_only, args.test_name_filter, gdbinit_path)
|
|
||||||
run_tests_and_print_stats(tests, args, gdbinit_path)
|
if args.type == "gdb":
|
||||||
|
ensureZigPath()
|
||||||
|
makeBinaries()
|
||||||
|
else:
|
||||||
|
makeCrossArchBinaries()
|
||||||
|
gdb_binary = "gdb-multiarch"
|
||||||
|
|
||||||
|
test_dir_path = TEST_FOLDER_NAME[args.type]
|
||||||
|
|
||||||
|
tests: List[str] = getTestsList(
|
||||||
|
args.collect_only, args.test_name_filter, gdb_binary, gdbinit_path, test_dir_path
|
||||||
|
)
|
||||||
|
|
||||||
|
ports = []
|
||||||
|
if args.type == "cross-arch":
|
||||||
|
ports = open_ports(len(tests))
|
||||||
|
|
||||||
|
run_tests_and_print_stats(tests, args, gdb_binary, gdbinit_path, test_dir_path, ports)
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
COV=0
|
||||||
|
# Run unit tests
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ "$arg" == "--cov" ]; then
|
||||||
|
COV=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $COV -eq 1 ]; then
|
||||||
|
coverage run -m pytest tests/unit-tests
|
||||||
|
else
|
||||||
|
pytest tests/unit-tests
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit_code=$((exit_code + $?))
|
||||||
|
|
||||||
|
exit $exit_code
|
Loading…
Reference in New Issue