diff --git a/.github/tests/entrypoint.sh b/.github/tests/entrypoint.sh new file mode 100644 index 000000000..c50e8d3f8 --- /dev/null +++ b/.github/tests/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -x + +PY_VER=$(gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit 2>/dev/null || echo "3") +GEF_CI_NB_CPU=$(grep -c ^processor /proc/cpuinfo) + +# Setup GEF +echo "source /gef/gef.py" > /root/.gdbinit + +# Verify GEF setup +gdb -q -ex "gef missing" -ex "gef help" -ex "gef config" -ex start -ex continue -ex quit /bin/pwd + +# Build test binaries +make -C tests/binaries -j ${GEF_CI_NB_CPU} + +# Run pytest +python${PY_VER} -m pytest --forked -n ${GEF_CI_NB_CPU} -v -m "not benchmark" tests/ diff --git a/.github/tests/run-tests.sh b/.github/tests/run-tests.sh new file mode 100755 index 000000000..bfde36092 --- /dev/null +++ b/.github/tests/run-tests.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +[ -z "${1}" ] && echo "Provide a container tag as an argument to this script" && exit 1 + +docker run --privileged --rm -e GITHUB_ACTIONS -v "$PWD:/gef" gef-test:${1} diff --git a/.github/tests/setup-dockerfile.sh b/.github/tests/setup-dockerfile.sh new file mode 100755 index 000000000..b8169909e --- /dev/null +++ b/.github/tests/setup-dockerfile.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +[ -z "${1}" ] && echo "Provide a container image as an argument to this script" && exit 1 + +echo "FROM ${1}" > Dockerfile + +cat >> Dockerfile << 'EOF' + +# Install dependencies for Ubuntu-based images +RUN if [ -f /etc/debian_version ]; then \ + export DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=n && \ + apt-get update && \ + apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools \ + git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user file; \ +fi + +# Install python3-full for Ubuntu 24.04 +RUN if grep -q "24.04" /etc/os-release 2>/dev/null; then \ + apt-get install -y python3-full; \ +fi + +# Install dependencies for Fedora-based images +RUN if [ -f /etc/fedora-release ]; then \ + dnf install -y gdb gdb-gdbserver python3-devel python3-pip python3-wheel python3-setuptools python3-rpm \ + git cmake gcc gcc-c++ pkg-config glib2-devel qemu-user qemu-user-static file procps-ng && \ + dnf --enablerepo='*debug*' install -y glibc-debuginfo && \ + dnf clean all; \ +fi + +# Copy only requirements.txt for caching +COPY tests/requirements.txt /tmp/requirements.txt + +# Install Python requirements +RUN PY_VER=$(gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit 2>/dev/null || echo "3") && \ + if grep -q "24.04" /etc/os-release 2>/dev/null; then \ + python${PY_VER} -m pip install --break-system-packages --upgrade -r /tmp/requirements.txt; \ + else \ + python${PY_VER} -m pip install --upgrade -r /tmp/requirements.txt; \ + fi + +RUN git config --global --add safe.directory /gef + +WORKDIR /gef + +# Copy entrypoint script +COPY .github/tests/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] + +EOF diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dd052dd56..396a83b0a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,8 +1,6 @@ name: Tests env: - GEF_CI_CACHE_DIR: /tmp - PY_VER: 0 GEF_CI_NB_CPU: 1 on: @@ -19,14 +17,22 @@ jobs: strategy: fail-fast: false matrix: - runner: - - ubuntu-24.04 - - ubuntu-22.04 - - ubuntu-24.04-arm - - ubuntu-22.04-arm - - name: "Run Unit tests on ${{ matrix.runner }}" - runs-on: ${{ matrix.runner }} + arch: [amd64, arm64] + os: [ubuntu-24.04, ubuntu-22.04, fedora-42, fedora-43, fedora-rawhide] + include: + - os: ubuntu-24.04 + base_image: ubuntu:24.04 + - os: ubuntu-22.04 + base_image: ubuntu:22.04 + - os: fedora-42 + base_image: fedora:42 + - os: fedora-43 + base_image: fedora:43 + - os: fedora-rawhide + base_image: fedora:rawhide + + name: "Run Unit tests on ${{ matrix.os }} (${{ matrix.arch }})" + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} defaults: run: shell: bash @@ -34,63 +40,21 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install python and toolchain - run: | - export NEEDRESTART_MODE=n - sudo apt-get update - sudo apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user - - - name: Install python and toolchain - if: startsWith(matrix.runner, 'ubuntu-24.04') - run: | - sudo apt-get install -y python3-full + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Install python and toolchain - if: startsWith(matrix.runner, 'ubuntu-24.04') == false - run: | - python3 -m pip install pip -U + - name: Create Dockerfile + run: .github/tests/setup-dockerfile.sh ${{ matrix.base_image }} - - name: Set runtime environment variables - run: | - echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV - echo GEF_CI_NB_CPU="`grep -c ^processor /proc/cpuinfo`" >> $GITHUB_ENV - echo GEF_CI_ARCH="`uname --processor`" >> $GITHUB_ENV - echo GEF_CI_CACHE_DIR="`python3 -m pip cache dir`" >> $GITHUB_ENV - - - name: Cache dependencies - uses: actions/cache@v4 - id: cache-deps - env: - cache-name: cache-deps + - name: Build container image + uses: docker/build-push-action@v6 with: - key: ${{ matrix.runner }}-pip-${{ hashFiles('**/requirements.txt') }} - path: ${{ env.GEF_CI_CACHE_DIR }} - restore-keys: - ${{ matrix.runner }}-pip-${{ env.cache-name }}- - ${{ matrix.runner }}-pip- - ${{ matrix.runner }}-${{ env.cache-name }}- - ${{ matrix.runner }}- - - - name: Install python and toolchain - if: startsWith(matrix.runner, 'ubuntu-24.04') == false - run: | - test "${{ env.PY_VER }}" == "0" && exit 1 - mkdir -p ${{ env.GEF_CI_CACHE_DIR }} - python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt - - - name: Install Python Requirements - if: startsWith(matrix.runner, 'ubuntu-24.04') - run: | - test "${{ env.PY_VER }}" == "0" && exit 1 - mkdir -p ${{ env.GEF_CI_CACHE_DIR }} - python${{ env.PY_VER }} -m pip install --break-system-packages --user --upgrade -r tests/requirements.txt - - - name: Setup GEF - run: | - echo "source $(pwd)/gef.py" > ~/.gdbinit - gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd - - - name: Run Tests - run: | - make -C tests/binaries -j ${{ env.GEF_CI_NB_CPU }} - python${{ env.PY_VER }} -m pytest --forked -n ${{ env.GEF_CI_NB_CPU }} -v -m "not benchmark" tests/ + context: . + push: false + load: true + tags: gef-test:${{ matrix.os }}-${{ matrix.arch }} + cache-from: type=gha,scope=${{ matrix.os }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=${{ matrix.os }}-${{ matrix.arch }} + + - name: Run tests in container + run: .github/tests/run-tests.sh ${{ matrix.os }}-${{ matrix.arch }} diff --git a/tests/commands/heap.py b/tests/commands/heap.py index 6073c22d4..d6a70e042 100644 --- a/tests/commands/heap.py +++ b/tests/commands/heap.py @@ -9,6 +9,7 @@ ERROR_INACTIVE_SESSION_MESSAGE, debug_target, findlines, + is_glibc_ge, is_32b, is_64b, ) @@ -21,6 +22,7 @@ def setUp(self) -> None: self._target = debug_target("heap") return super().setUp() + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_arenas(self): gdb = self._gdb cmd = "heap arenas" @@ -32,6 +34,7 @@ def test_cmd_heap_arenas(self): res = gdb.execute(cmd, to_string=True) self.assertIn("Arena(base=", res) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_set_arena(self): gdb = self._gdb cmd = "heap set-arena &main_arena" @@ -67,6 +70,7 @@ def test_cmd_heap_chunk_with_number(self): chunklines = findlines("Chunk(addr=", res) self.assertEqual(len(chunklines), 2) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_chunks(self): gdb = self._gdb cmd = "heap chunks" @@ -91,6 +95,7 @@ def test_cmd_heap_chunks_summary(self): self.assertIn("== Chunk distribution by size", res) self.assertIn("== Chunk distribution by flag", res) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_chunks_min_size_filter(self): gdb = self._gdb self.assertEqual( @@ -148,6 +153,7 @@ def setUp(self) -> None: self._target = debug_target("heap-non-main") return super().setUp() + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_chunks(self): gdb = self._gdb cmd = "heap chunks" @@ -169,6 +175,7 @@ def test_cmd_heap_chunks(self): # make sure that the chunks of each arena are distinct self.assertNotEqual(chunks, non_main_chunks) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_bins_non_main(self): gdb = self._gdb gef = self._gef @@ -181,6 +188,7 @@ def test_cmd_heap_bins_non_main(self): self.assertIn("size=0x20", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64",), reason=f"Skipped for {ARCH}") + @pytest.mark.skipif(is_glibc_ge(2, 42), reason="Skipped for glibc >= 2.42") def test_cmd_heap_bins_tcache(self): gdb = self._gdb gdb.execute("run") @@ -202,6 +210,7 @@ def setUp(self) -> None: self._target = debug_target("heap-multiple-heaps") return super().setUp() + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_chunks_mult_heaps(self): gdb = self._gdb @@ -259,6 +268,7 @@ def setUp(self) -> None: self.expected_unsorted_bin_size = 0x430 if ARCH == "i686" or is_64b() else 0x428 return super().setUp() + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_bins_large(self): gdb = self._gdb gdb.execute("run") @@ -268,6 +278,7 @@ def test_cmd_heap_bins_large(self): self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_large_bin_size:#x}", res) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_bins_small(self): gdb = self._gdb cmd = "heap bins small" @@ -278,6 +289,7 @@ def test_cmd_heap_bins_small(self): self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_small_bin_size:#x}", res) + @pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43") def test_cmd_heap_bins_unsorted(self): gdb = self._gdb gdb.execute("run") @@ -295,6 +307,7 @@ def setUp(self) -> None: return super().setUp() @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") + @pytest.mark.skipif(is_glibc_ge(2, 42), reason="Skipped for glibc >= 2.42") def test_cmd_heap_bins_tcache_all(self): gdb = self._gdb gdb.execute("run") diff --git a/tests/commands/scan.py b/tests/commands/scan.py index 18372463d..c5a95f1c8 100644 --- a/tests/commands/scan.py +++ b/tests/commands/scan.py @@ -2,9 +2,10 @@ scan command test module """ +import pytest from tests.base import RemoteGefUnitTestGeneric -from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target +from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, IN_GITHUB_ACTIONS, debug_target, is_glibc_ge class ScanCommand(RemoteGefUnitTestGeneric): @@ -14,6 +15,7 @@ def setUp(self) -> None: self._target = debug_target("default") return super().setUp() + @pytest.mark.skipif(ARCH == "aarch64" and IN_GITHUB_ACTIONS and is_glibc_ge(2, 41), reason=f"Skipped for {ARCH} on CI with glibc >= 2.41") def test_cmd_scan(self): gdb = self._gdb cmd = "scan libc stack" diff --git a/tests/utils.py b/tests/utils.py index 12d1d1abf..603812829 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -78,6 +78,14 @@ class Color(enum.Enum): BLINK_OFF = "\001\033[25m\002" +def is_glibc_ge(major, minor): + ver = platform.libc_ver() + if ver[0] == 'glibc': + (glibc_major, glibc_minor, *glibc_patch) = list(map(int, ver[1].split('.'))) + return (glibc_major, glibc_minor) >= (major, minor) + return False + + def is_64b() -> bool: return ARCH in CI_VALID_ARCHITECTURES_64B