From 78d6b7b2dc59eecea864e7ba43f8db9606e26751 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 19 Oct 2026 12:18:07 +0200 Subject: [PATCH] Added reactor github actions job and test suite - added verbose output when file is detected inside file_watcher, for the test to read - created a parallel github actions job for reactor tests - created a reactor test suite, with helper functions and main 'run' script Ticket: ENT-14731 Signed-off-by: Victor Moene --- .github/workflows/ci.yml | 3 ++ .github/workflows/reactor_tests.yml | 25 +++++++++++++ cf-reactor/file_watcher.c | 5 +++ tests/reactor/lib.cf | 7 ++++ tests/reactor/lib.sh | 54 +++++++++++++++++++++++++++++ tests/reactor/run.sh | 43 +++++++++++++++++++++++ tests/reactor/tests/001_vars.cf | 24 +++++++++++++ tests/reactor/tests/001_vars.sh | 24 +++++++++++++ 8 files changed, 185 insertions(+) create mode 100644 .github/workflows/reactor_tests.yml create mode 100644 tests/reactor/lib.cf create mode 100644 tests/reactor/lib.sh create mode 100644 tests/reactor/run.sh create mode 100644 tests/reactor/tests/001_vars.cf create mode 100644 tests/reactor/tests/001_vars.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36bcf11f061..7886e046581 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,9 @@ jobs: acceptance_tests: needs: unit_tests uses: ./.github/workflows/acceptance_tests.yml + reactor_tests: + needs: unit_tests + uses: ./.github/workflows/reactor_tests.yml macos_unit_tests: needs: unit_tests uses: ./.github/workflows/macos_unit_tests.yml diff --git a/.github/workflows/reactor_tests.yml b/.github/workflows/reactor_tests.yml new file mode 100644 index 00000000000..b182dbe6796 --- /dev/null +++ b/.github/workflows/reactor_tests.yml @@ -0,0 +1,25 @@ +name: Reactor Tests + +on: + workflow_call + +jobs: + reactor_tests: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: ./ci/dependencies.sh + - name: Run autotools / configure + run: ./autogen.sh --enable-debug + - name: Compile and link (make) + run: make -j8 CFLAGS="-Werror -Wall" + - name: Run reactor tests + run: | + mkdir -p "$RUNNER_TEMP/bin" + ln -s "$PWD/cf-reactor/cf-reactor" "$PWD/cf-promises/cf-promises" "$RUNNER_TEMP/bin/" + cd tests/reactor + chmod -R go-w . + CF_BINDIR="$RUNNER_TEMP/bin" sh run.sh diff --git a/cf-reactor/file_watcher.c b/cf-reactor/file_watcher.c index 869df2d7cab..c383ba1b883 100644 --- a/cf-reactor/file_watcher.c +++ b/cf-reactor/file_watcher.c @@ -164,6 +164,11 @@ WatcherCheckResult FileWatcherCheckFileDeleted(void *state) return WATCHER_CHECK_ERROR; } + if (exists && !fws->last_exists) + { + Log(LOG_LEVEL_VERBOSE, "File '%s' is present, watching for its deletion", fws->path); + } + bool deleted = fws->last_exists && !exists; return FileWatcherRecordObservation(fws, exists, deleted, "deletion"); } diff --git a/tests/reactor/lib.cf b/tests/reactor/lib.cf new file mode 100644 index 00000000000..d1f11667a5a --- /dev/null +++ b/tests/reactor/lib.cf @@ -0,0 +1,7 @@ +bundle agent record_event(file, message) +{ + reports: + "$(with): $(message)" + with => strftime("localtime", "%Y-%m-%d %H:%M:%S", now()), + report_to_file => "$(file)"; +} diff --git a/tests/reactor/lib.sh b/tests/reactor/lib.sh new file mode 100644 index 00000000000..6b24ef43038 --- /dev/null +++ b/tests/reactor/lib.sh @@ -0,0 +1,54 @@ +# Helpers sourced by the tests. Expects CF_BINDIR and LOG to be exported by +# run.sh. + +# number of log lines already consumed by wait_for_log +log_offset=0 + +# Wait until the reactor log contains $1 in a line after the last match, then +# skip past that line, so that the next wait only sees newer output. +wait_for_log() { + if ! match=$(timeout 60 sh -c ' + until tail -n "+$(($2 + 1))" "$1" | grep -n -F -m 1 -- "$3"; do + sleep 0.1 + done' _ "$LOG" "$log_offset" "$1") + then + echo "Timed out waiting for '$1' in reactor log:" >&2 + cat "$LOG" >&2 + return 1 + fi + log_offset=$((log_offset + ${match%%:*})) +} + +# Wait until file $1 has at least $2 lines, then fail if it has more than $2. +wait_for_lines() { + if ! timeout 60 sh -c ' + until [ "$(cat "$1" 2>/dev/null | wc -l)" -ge "$2" ]; do + sleep 0.1 + done' _ "$1" "$2" + then + echo "Timed out waiting for $2 line(s) in '$1', reactor log:" >&2 + cat "$LOG" >&2 + return 1 + fi + + lines=$(wc -l < "$1") + if [ "$lines" -ne "$2" ] + then + echo "Expected $2 line(s) in '$1', got $lines" >&2 + return 1 + fi +} + +# Start cf-reactor in the background with policy file $1, logging to $LOG. +# It is stopped when the test exits. +start_reactor() { + "$CF_BINDIR/cf-reactor" --no-fork --verbose -f "$1" >>"$LOG" 2>&1 & + reactor_pid=$! + trap stop_reactor EXIT +} + +stop_reactor() { + echo "Exiting cf-reactor..." + kill -TERM "$reactor_pid" 2>/dev/null || true + wait "$reactor_pid" 2>/dev/null || true +} diff --git a/tests/reactor/run.sh b/tests/reactor/run.sh new file mode 100644 index 00000000000..e6acc659a3d --- /dev/null +++ b/tests/reactor/run.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +fail=0 + +banner() { + name=" $(basename "$1") " + width=72 + left=$(( (width - ${#name}) / 2 )) + right=$(( width - ${#name} - left )) + [ "$left" -lt 0 ] && left=0 + [ "$right" -lt 0 ] && right=0 + printf '%s%s%s\n' \ + "$(printf "%${left}s" '' | tr ' ' '=')" \ + "$name" \ + "$(printf "%${right}s" '' | tr ' ' '=')" +} + +error() { + echo "test '$1' FAILED" >&2 + fail=1 +} + +CF_BINDIR=${CF_BINDIR:-/var/cfengine/bin} +export CF_BINDIR + +for t in "$PWD"/tests/*.sh; do + banner "$t" + + # create throwaway work dir + WORKDIR=$(mktemp -d) + mkdir -p "$WORKDIR/bin" "$WORKDIR/tmp" + ln -s "$CF_BINDIR/cf-promises" "$WORKDIR/bin/" + CFENGINE_TEST_OVERRIDE_WORKDIR="$WORKDIR" + LOG="$WORKDIR/reactor.log" + PROMISE_NAME=$(basename "$t" .sh) + export WORKDIR CFENGINE_TEST_OVERRIDE_WORKDIR LOG PROMISE_NAME + + sh "$t" "${t%.sh}.cf" || error "$t" + + rm -rf "$WORKDIR" +done + +exit $fail diff --git a/tests/reactor/tests/001_vars.cf b/tests/reactor/tests/001_vars.cf new file mode 100644 index 00000000000..43d1400ca09 --- /dev/null +++ b/tests/reactor/tests/001_vars.cf @@ -0,0 +1,24 @@ +body common control +{ + inputs => { "../lib.cf" }; + bundlesequence => { "test" }; + version => "1.0"; +} + +body when deleted(file) +{ + file_deleted => "$(file)"; +} + +bundle reactor test +{ + vars: + "test" string => getenv("TESTFILE", 999); + "events_file" string => getenv("EVENTS_FILE", 999); + "promise_name" string => basename("$(this.promise_filename)", ".cf"); + + events: + "$(promise_name)" + when => deleted("$(test)"), + then => record_event("$(events_file)", "$(test) was deleted"); +} diff --git a/tests/reactor/tests/001_vars.sh b/tests/reactor/tests/001_vars.sh new file mode 100644 index 00000000000..588e951218f --- /dev/null +++ b/tests/reactor/tests/001_vars.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +set -ex + +. "$(dirname "$0")/../lib.sh" + +TESTFILE="$WORKDIR/tmp/test" +EVENTS_FILE="$WORKDIR/tmp/events" +export TESTFILE EVENTS_FILE + +# create initial file +touch "$TESTFILE" + +start_reactor "$1" + +wait_for_log "File '$TESTFILE' is present, watching for its deletion" +rm "$TESTFILE" +wait_for_lines "$EVENTS_FILE" 1 + +# recreate the file and wait for the watcher to see it before deleting it again +touch "$TESTFILE" +wait_for_log "File '$TESTFILE' is present, watching for its deletion" +rm "$TESTFILE" +wait_for_lines "$EVENTS_FILE" 2