From 7242e785d85de826d73d2dacdc3b0d4f07ba17e0 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Wed, 3 Jun 2026 18:57:04 +0200 Subject: [PATCH 1/8] tests: add Python 3.9 compatibility Add support for running integration tests on Python 3.9 through 3.14+ with minimal changes using modern Python features. Changes: - event.py: Add __future__ annotations import to enable modern type hint syntax (int | None) on Python 3.9, with fallback for @override decorator (Python 3.12+) - test_config_hotreload.py: Handle both builtin TimeoutError (Python 3.11+) and concurrent.futures.TimeoutError (Python 3.9-3.10) - test_path_rmdir.py: Reverse event order for Python < 3.10 due to different shutil.rmtree deletion ordering This avoids using deprecated Union/Optional types while maintaining backward compatibility. Tested: 102 tests pass on Python 3.9.25 Assisted-by: Claude Code (claude-sonnet-4-5@20250929) --- tests/event.py | 12 +++++++++++- tests/test_config_hotreload.py | 5 +++-- tests/test_path_rmdir.py | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/event.py b/tests/event.py index 11bca769..811e19a4 100644 --- a/tests/event.py +++ b/tests/event.py @@ -1,8 +1,18 @@ +from __future__ import annotations + import os import string from enum import Enum from re import Pattern -from typing import Any, override +from typing import Any + +try: + from typing import override # type: ignore[reportAssignmentType] +except ImportError: + + def override(func): # type: ignore[reportMissingParameterType] + return func + import utils from internalapi.sensor.collector_pb2 import ProcessSignal diff --git a/tests/test_config_hotreload.py b/tests/test_config_hotreload.py index 996f4b0f..f9aa3d91 100644 --- a/tests/test_config_hotreload.py +++ b/tests/test_config_hotreload.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from concurrent.futures import TimeoutError as FuturesTimeoutError from time import sleep import docker.models.containers @@ -219,7 +220,7 @@ def test_no_paths_then_add( e = Event(process=p, event_type=EventType.OPEN, file=fut, host_path=fut) - with pytest.raises(TimeoutError): + with pytest.raises((TimeoutError, FuturesTimeoutError)): server.wait_events([e]) # Add paths back @@ -264,7 +265,7 @@ def test_paths_then_remove( f.write('This should be ignored') sleep(1) - with pytest.raises(TimeoutError): + with pytest.raises((TimeoutError, FuturesTimeoutError)): server.wait_events([e]) diff --git a/tests/test_path_rmdir.py b/tests/test_path_rmdir.py index 669699d4..a61d9316 100644 --- a/tests/test_path_rmdir.py +++ b/tests/test_path_rmdir.py @@ -2,6 +2,7 @@ import os import shutil +import sys import pytest @@ -252,6 +253,10 @@ def test_rmdir_recursive( ), ] + # shutil.rmtree changed unlink order in Python 3.10 + if sys.version_info < (3, 10): # noqa: UP036 + unlink_events.reverse() + server.wait_events(unlink_events) # Check that all inodes and kernel events were tracked From adceceaf49e9c81c0042edc5e4ef4522ed29795c Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Wed, 3 Jun 2026 18:57:36 +0200 Subject: [PATCH 2/8] feat: run integration tests on more platforms --- .github/workflows/integration-tests.yml | 19 ++++++- ansible/group_vars/all.yml | 1 + ansible/group_vars/platform_rhcos.yml | 1 + ansible/group_vars/platform_rhcos_arm64.yml | 1 + ansible/group_vars/platform_rhel.yml | 2 + ansible/group_vars/platform_rhel_arm64.yml | 2 + ansible/run-tests.yml | 63 +++++++++++---------- 7 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 ansible/group_vars/platform_rhel.yml create mode 100644 ansible/group_vars/platform_rhel_arm64.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f32e77f8..b3c2164d 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -27,6 +27,9 @@ jobs: vm: - fedora-coreos - fcarm + - rhel + - rhel-arm64 + - rhcos steps: - uses: actions/checkout@v4 @@ -80,11 +83,25 @@ jobs: quay: username: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} password: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + excluded_vms: + # RHEL 8 doesn't handle file creation properly, + # need more investigation + - rhel-8 + - rhcos-412-86-202402272018-0-gcp-x86-64 + - rhcos-414-92-202407091253-0-gcp-x86-64 + # BPF trampolines are only implemented starting with RHEL 10 + - rhel-9-arm64 EOF - name: Create Test VMs + env: + ANSIBLE_CONFIG: "${{ github.workspace }}/collector/ansible/ansible.cfg" run: | - make -C "./collector/ansible" create-ci-vms + ansible-playbook \ + -i "${GITHUB_WORKSPACE}/collector/ansible/ci" \ + -e @vars.yml \ + --tags setup,provision \ + "${GITHUB_WORKSPACE}/collector/ansible/integration-tests.yml" - name: Run the tests env: diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index cfc86685..39fdc2fb 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -1,2 +1,3 @@ --- runtime_command: docker +runtime_host: 'unix://var/run/docker.sock' diff --git a/ansible/group_vars/platform_rhcos.yml b/ansible/group_vars/platform_rhcos.yml index 792d9227..35a6f0ad 100644 --- a/ansible/group_vars/platform_rhcos.yml +++ b/ansible/group_vars/platform_rhcos.yml @@ -1,2 +1,3 @@ --- ansible_user: core +runtime_host: "unix://run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhcos_arm64.yml b/ansible/group_vars/platform_rhcos_arm64.yml index 792d9227..35a6f0ad 100644 --- a/ansible/group_vars/platform_rhcos_arm64.yml +++ b/ansible/group_vars/platform_rhcos_arm64.yml @@ -1,2 +1,3 @@ --- ansible_user: core +runtime_host: "unix://run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel.yml b/ansible/group_vars/platform_rhel.yml new file mode 100644 index 00000000..bad6bd3d --- /dev/null +++ b/ansible/group_vars/platform_rhel.yml @@ -0,0 +1,2 @@ +--- +runtime_host: "unix://run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel_arm64.yml b/ansible/group_vars/platform_rhel_arm64.yml new file mode 100644 index 00000000..bad6bd3d --- /dev/null +++ b/ansible/group_vars/platform_rhel_arm64.yml @@ -0,0 +1,2 @@ +--- +runtime_host: "unix://run/podman/podman.sock" diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 20578442..2eba5998 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -5,57 +5,58 @@ FACT_IMAGE_NAME: "{{ fact.image | default(None) }}" tasks: - - name: Install dependencies - become: true - community.general.rpm_ostree_pkg: - apply_live: true - name: - - make - - python3-packaging - - python3-requests - state: present - - - name: Login to quay.io - community.docker.docker_login: - registry_url: quay.io - username: "{{ quay.username }}" - password: "{{ quay.password }}" - - name: Clone the repo ansible.builtin.git: repo: https://github.com/stackrox/fact - dest: ./fact + dest: /tmp/fact version: "{{ fact.version }}" update: false - - name: Install python packages - ansible.builtin.pip: - requirements: ./fact/tests/requirements.txt - chdir: "{{ ansible_env.HOME }}" - virtualenv: ./fact/.venv - virtualenv_command: python3 -m venv - - block: + # There are some ansible modules that we could use to modularize + # this next task, however they required some Python modules to be + # installed on the managed VM that may not be available (like + # python3-packaging), so we stick to a shell as ugly as it is. - name: Run tests + become: true + environment: + DOCKER_HOST: "{{ runtime_host }}" ansible.builtin.shell: cmd: | - cd "${HOME}/fact" - source ".venv/bin/activate" - make integration-tests + cd "/tmp/fact/tests" + + # Setup the virtual environment + python3 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + + # Generate gRPC files + python3 -m grpc_tools.protoc \ + -I../third_party/stackrox/proto \ + --python_out=. \ + --pyi_out=. \ + --grpc_python_out=. \ + ../third_party/stackrox/proto/internalapi/sensor/collector.proto \ + ../third_party/stackrox/proto/internalapi/sensor/sfa.proto \ + ../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto + + # Run the tests + pytest --image="${FACT_IMAGE_NAME}" --junit-xml=results.xml + always: - name: Retrieve results ansible.builtin.fetch: - src: "{{ ansible_env.HOME }}/fact/tests/results.xml" + src: "/tmp/fact/tests/results.xml" dest: ../tests/ flat: true - name: Compress log files community.general.archive: - path: "{{ ansible_env.HOME }}/fact/tests/logs" - dest: "{{ ansible_env.HOME }}/fact/tests/logs.tar.gz" + path: "/tmp/fact/tests/logs" + dest: "/tmp/fact/tests/logs.tar.gz" - name: Fetch log files ansible.builtin.fetch: - src: "{{ ansible_env.HOME }}/fact/tests/logs.tar.gz" + src: "/tmp/fact/tests/logs.tar.gz" dest: ../tests/logs.tar.gz flat: true From 1d09560a27120608e5081e8a9ac7cedb1510bd46 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 10:25:48 +0200 Subject: [PATCH 3/8] fix: test_cross_mountpoints skips ownership events when users match --- tests/test_path_rename.py | 56 +++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/tests/test_path_rename.py b/tests/test_path_rename.py index 21fd263f..5445c76e 100644 --- a/tests/test_path_rename.py +++ b/tests/test_path_rename.py @@ -451,20 +451,27 @@ def test_cross_mountpoints( container_id=test_container.id[:12], ) - server.wait_events( - [ - Event( - process=touch, - event_type=EventType.OPEN, - file=mounted_file, - host_path=host_path, - ), - Event( - process=first_rename, - event_type=EventType.CREATION, - file=ovfs_file, - host_path='', - ), + events = [ + Event( + process=touch, + event_type=EventType.OPEN, + file=mounted_file, + host_path=host_path, + ), + Event( + process=first_rename, + event_type=EventType.CREATION, + file=ovfs_file, + host_path='', + ), + ] + + # If the id for the current process and the process in the container (root) + # match, the ownership events are skipped + curr_id = (os.getuid(), os.getgid()) + root_id = (0, 0) + if curr_id != root_id: + events.append( Event( process=first_rename, event_type=EventType.OWNERSHIP, @@ -472,7 +479,11 @@ def test_cross_mountpoints( host_path='', owner_uid=owner_uid, owner_gid=owner_gid, - ), + ) + ) + + events.extend( + [ Event( process=first_rename, event_type=EventType.PERMISSION, @@ -492,6 +503,11 @@ def test_cross_mountpoints( file=mounted_file, host_path=host_path, ), + ] + ) + + if curr_id != root_id: + events.append( Event( process=second_rename, event_type=EventType.OWNERSHIP, @@ -499,7 +515,11 @@ def test_cross_mountpoints( host_path=host_path, owner_uid=owner_uid, owner_gid=owner_gid, - ), + ) + ) + + events.extend( + [ Event( process=second_rename, event_type=EventType.PERMISSION, @@ -513,5 +533,7 @@ def test_cross_mountpoints( file=ovfs_file, host_path='', ), - ], + ] ) + + server.wait_events(events) From 2e4730084cb2531d8793ca03a9631363bc316366 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 10:54:07 +0200 Subject: [PATCH 4/8] Minor fixes --- ansible/group_vars/all.yml | 2 +- ansible/group_vars/platform_rhcos.yml | 2 +- ansible/group_vars/platform_rhcos_arm64.yml | 2 +- ansible/group_vars/platform_rhel.yml | 2 +- ansible/group_vars/platform_rhel_arm64.yml | 2 +- ansible/run-tests.yml | 1 + 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index 39fdc2fb..746de296 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -1,3 +1,3 @@ --- runtime_command: docker -runtime_host: 'unix://var/run/docker.sock' +runtime_host: 'unix:///var/run/docker.sock' diff --git a/ansible/group_vars/platform_rhcos.yml b/ansible/group_vars/platform_rhcos.yml index 35a6f0ad..fd7510c0 100644 --- a/ansible/group_vars/platform_rhcos.yml +++ b/ansible/group_vars/platform_rhcos.yml @@ -1,3 +1,3 @@ --- ansible_user: core -runtime_host: "unix://run/podman/podman.sock" +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhcos_arm64.yml b/ansible/group_vars/platform_rhcos_arm64.yml index 35a6f0ad..fd7510c0 100644 --- a/ansible/group_vars/platform_rhcos_arm64.yml +++ b/ansible/group_vars/platform_rhcos_arm64.yml @@ -1,3 +1,3 @@ --- ansible_user: core -runtime_host: "unix://run/podman/podman.sock" +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel.yml b/ansible/group_vars/platform_rhel.yml index bad6bd3d..945e3538 100644 --- a/ansible/group_vars/platform_rhel.yml +++ b/ansible/group_vars/platform_rhel.yml @@ -1,2 +1,2 @@ --- -runtime_host: "unix://run/podman/podman.sock" +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/group_vars/platform_rhel_arm64.yml b/ansible/group_vars/platform_rhel_arm64.yml index bad6bd3d..945e3538 100644 --- a/ansible/group_vars/platform_rhel_arm64.yml +++ b/ansible/group_vars/platform_rhel_arm64.yml @@ -1,2 +1,2 @@ --- -runtime_host: "unix://run/podman/podman.sock" +runtime_host: "unix:///run/podman/podman.sock" diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 2eba5998..08070c66 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -23,6 +23,7 @@ DOCKER_HOST: "{{ runtime_host }}" ansible.builtin.shell: cmd: | + set -euo pipefail cd "/tmp/fact/tests" # Setup the virtual environment From 34fff35be69c1b3f31cb4f71a070bd3f6e37064d Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 11:46:50 +0200 Subject: [PATCH 5/8] skip test_rmdir_recursive on Python < 3.10 --- tests/test_path_rmdir.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_path_rmdir.py b/tests/test_path_rmdir.py index a61d9316..0dba5780 100644 --- a/tests/test_path_rmdir.py +++ b/tests/test_path_rmdir.py @@ -155,6 +155,10 @@ def test_rmdir_empty( ) +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason='shutil.rmtree behavior changes between interpreter versions', +) def test_rmdir_recursive( monitored_dir: str, server: FileActivityService, @@ -253,10 +257,6 @@ def test_rmdir_recursive( ), ] - # shutil.rmtree changed unlink order in Python 3.10 - if sys.version_info < (3, 10): # noqa: UP036 - unlink_events.reverse() - server.wait_events(unlink_events) # Check that all inodes and kernel events were tracked From 9c86da0fbe666e3f22787c38cfc4fbb24234d8d9 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 11:47:26 +0200 Subject: [PATCH 6/8] Use specific ansible module for login to quay.io --- ansible/roles/quay-login/tasks/main.yml | 15 +++++++++++++++ ansible/run-tests.yml | 4 ++++ 2 files changed, 19 insertions(+) create mode 100644 ansible/roles/quay-login/tasks/main.yml diff --git a/ansible/roles/quay-login/tasks/main.yml b/ansible/roles/quay-login/tasks/main.yml new file mode 100644 index 00000000..dda6fa6d --- /dev/null +++ b/ansible/roles/quay-login/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Login with docker + community.docker.docker_login: + registry_url: quay.io + username: '{{ quay.username }}' + password: '{{ quay.password }}' + when: runtime_command == 'docker' + +- name: Login with podman + become: true + containers.podman.podman_login: + registry: quay.io + username: '{{ quay.username }}' + password: '{{ quay.password }}' + when: runtime_command == 'podman' diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 08070c66..b3cb83dc 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -12,6 +12,10 @@ version: "{{ fact.version }}" update: false + - name: Login to quay.io + ansible.builtin.include_role: + name: quay-login + - block: # There are some ansible modules that we could use to modularize # this next task, however they required some Python modules to be From bcfe3729761611e044afd0bcbe5f5cd6caadfc68 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 15:46:59 +0200 Subject: [PATCH 7/8] Yet another way to login to quay.io --- ansible/roles/quay-login/tasks/main.yml | 15 --------------- ansible/run-tests.yml | 8 +++++--- 2 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 ansible/roles/quay-login/tasks/main.yml diff --git a/ansible/roles/quay-login/tasks/main.yml b/ansible/roles/quay-login/tasks/main.yml deleted file mode 100644 index dda6fa6d..00000000 --- a/ansible/roles/quay-login/tasks/main.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -- name: Login with docker - community.docker.docker_login: - registry_url: quay.io - username: '{{ quay.username }}' - password: '{{ quay.password }}' - when: runtime_command == 'docker' - -- name: Login with podman - become: true - containers.podman.podman_login: - registry: quay.io - username: '{{ quay.username }}' - password: '{{ quay.password }}' - when: runtime_command == 'podman' diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index b3cb83dc..7759e1dc 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -12,9 +12,11 @@ version: "{{ fact.version }}" update: false - - name: Login to quay.io - ansible.builtin.include_role: - name: quay-login + - name: Log into quay.io + become: "{{ runtime_command == 'podman' }}" + shell: + cmd: "{{ runtime_command }} login -u {{ quay.username }} --password-stdin quay.io" + stdin: "{{ quay.password }}" - block: # There are some ansible modules that we could use to modularize From 40363c4eac9e6382bcf9c594bf313e99a5445de1 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Tue, 9 Jun 2026 18:01:30 +0200 Subject: [PATCH 8/8] Try and fix podman login + logs from multiple machines --- .github/workflows/integration-tests.yml | 8 ++++---- ansible/run-tests.yml | 27 +++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b3c2164d..1f85c944 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -121,10 +121,10 @@ jobs: if: always() run: | cd "${GITHUB_WORKSPACE}/fact/tests" - if [[ -f "logs.tar.gz" ]]; then - tar xzf "logs.tar.gz" - rm -f "logs.tar.gz" - fi + for file in logs/*.tar.gz; do + tar xzf "$file" + rm -f "$file" + done - name: Test summary uses: test-summary/action@v2 diff --git a/ansible/run-tests.yml b/ansible/run-tests.yml index 7759e1dc..91115519 100644 --- a/ansible/run-tests.yml +++ b/ansible/run-tests.yml @@ -18,6 +18,23 @@ cmd: "{{ runtime_command }} login -u {{ quay.username }} --password-stdin quay.io" stdin: "{{ quay.password }}" + - name: Copy podman auth + become: true + shell: + cmd: | + mkdir -p ~/.docker/ + if [[ -f "${XDG_RUNTIME_DIR:-}/containers/auth.json" ]]; then + AUTH_FILE="${XDG_RUNTIME_DIR:-}/containers/auth.json" + elif [[ -f "/run/containers/0/auth.json" ]]; then + AUTH_FILE="/run/containers/0/auth.json" + else + echo &>2 "No valid auth.json file found" + exit 1 + fi + cp "${AUTH_FILE}" ~/.docker/config.json + creates: ~/.docker/config.json + when: runtime_command == "podman" + - block: # There are some ansible modules that we could use to modularize # this next task, however they required some Python modules to be @@ -51,6 +68,12 @@ pytest --image="${FACT_IMAGE_NAME}" --junit-xml=results.xml always: + - name: Make logs directories + file: + state: directory + path: "../tests/logs" + delegate_to: localhost + - name: Retrieve results ansible.builtin.fetch: src: "/tmp/fact/tests/results.xml" @@ -60,10 +83,10 @@ - name: Compress log files community.general.archive: path: "/tmp/fact/tests/logs" - dest: "/tmp/fact/tests/logs.tar.gz" + dest: "/tmp/fact/tests/{{ vm_config }}.tar.gz" - name: Fetch log files ansible.builtin.fetch: src: "/tmp/fact/tests/logs.tar.gz" - dest: ../tests/logs.tar.gz + dest: ../tests/logs flat: true