Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/bats.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Bats Integration Tests

on: [pull_request]

jobs:
bats:
runs-on: ubuntu-latest

env:
DB: postgresql

steps:
- uses: actions/checkout@v5

- name: Install bats
run: |
git clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git /tmp/bats-core
sudo /tmp/bats-core/install.sh /usr/local
bats --version

- name: Install podman
run: |
sudo apt-get update
sudo apt-get -y install podman
podman --version

- id: ruby_version
uses: voxpupuli/ruby-version@v1
- id: min_ruby
run: echo "version=$(echo '${{ steps.ruby_version.outputs.versions }}' | jq -r '.[-1]')" >> $GITHUB_OUTPUT
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
# Use the minimum supported Ruby version for rubocop (last in descending list)
ruby-version: ${{ steps.min_ruby.outputs.version }}
bundler-cache: true

- name: Pull container images
run: |
podman pull docker.io/library/postgres:15
podman pull docker.io/library/redis:7-alpine

- name: Run bats tests
run: bats -x --verbose-run --print-output-on-failure test/bats/

- name: Cleanup containers (if tests fail)
if: always()
run: |
podman stop dynflow-test-postgres dynflow-test-redis 2>/dev/null || true
podman rm -f dynflow-test-postgres dynflow-test-redis 2>/dev/null || true
2 changes: 1 addition & 1 deletion examples/example_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def persistence_adapter
end

def logger_adapter
Dynflow::LoggerAdapters::Simple.new $stderr, Logger::FATAL
Dynflow::LoggerAdapters::Simple.new $stdout, Logger::DEBUG
end

def run_web_console(world = ExampleHelper.world)
Expand Down
13 changes: 5 additions & 8 deletions examples/remote_executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,14 @@ def connector
Proc.new { |world| Dynflow::Connectors::Database.new(world) }
end

def run_client
def run_client(count)
world = ExampleHelper.create_world do |config|
config.persistence_adapter = persistence_adapter
config.executor = false
config.connector = connector
end

world.trigger(OrchestrateEvented::CreateInfrastructure)
world.trigger(OrchestrateEvented::CreateInfrastructure, true)

loop do
(count || 1000).times do
start_time = Time.now
world.trigger(SampleAction).finished.wait
finished_in = Time.now - start_time
Expand All @@ -150,13 +147,13 @@ def run_client
when 'server'
puts <<~MSG
The server is starting…. You can send the work to it by running:

#{$0} client

MSG
RemoteExecutorExample.run_server
when 'client'
RemoteExecutorExample.run_client
RemoteExecutorExample.run_client(ARGV[1]&.to_i)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nil.to_i is 0, which makes the condition above 0.times instead of probably desired 1000.times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nil.to_i is indeed 0, but nil&.to_i should still be nil, right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a dumbass, nevermind 🤦

else
puts "Unknown command #{comment}"
exit 1
Expand Down
67 changes: 67 additions & 0 deletions test/bats/helpers/common.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Common helper functions for bats tests

# Get the project root directory
get_project_root() {
local dir="${BATS_TEST_DIRNAME}"
while [ "${dir}" != "/" ]; do
if [ -f "${dir}/dynflow.gemspec" ]; then
echo "${dir}"
return 0
fi
dir="$(dirname "${dir}")"
done
echo "ERROR: Could not find project root" >&2
return 1
}

# Setup test environment variables
setup_test_env() {
export PROJECT_ROOT="$(get_project_root)"
export BUNDLE_GEMFILE="${PROJECT_ROOT}/Gemfile"

# Set database URLs for tests
export DATABASE_URL="$(get_postgres_url)"
export REDIS_URL="$(get_redis_url)"
export DB_CONN_STRING="$DATABASE_URL"

# Test directories
export TEST_PIDDIR="${BATS_TEST_TMPDIR}/pids"
}

run_background() {
local label="$1"
shift

local log_file="$(bg_output_file "$label")"
mkdir -p "$TEST_PIDDIR"
(
"$@" 2>&1 &
echo $! >"${TEST_PIDDIR}/${label}.pid"
) | tee "$log_file" | sed "s/^/${label}: /" &
}

bg_output_file() {
local label="$1"

echo "${BATS_TEST_TMPDIR}/${label}.log"
}

# A function that polls a given command until it succeeds or until it runs out
wait_for() {
local timeout="$1"
local interval="$2"
shift 2

local elapsed=0
while [ "$elapsed" -lt "$timeout" ]; do
if "$@" >/dev/null 2>&1; then
return 0
fi
sleep "$interval"
elapsed=$((elapsed + interval))
done

echo "Timeout after ${timeout}s waiting for: $*" >&2
return 1
}
146 changes: 146 additions & 0 deletions test/bats/helpers/containers.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
# Container helper functions for bats tests

# Default container names
POSTGRES_CONTAINER_NAME="${POSTGRES_CONTAINER_NAME:-dynflow-test-postgres}"
REDIS_CONTAINER_NAME="${REDIS_CONTAINER_NAME:-dynflow-test-redis}"

# Default ports
POSTGRES_PORT="${POSTGRES_PORT:-15432}"
REDIS_PORT="${REDIS_PORT:-16379}"

# Database credentials
POSTGRES_USER="${POSTGRES_USER:-dynflow_test}"
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-dynflow_test_pass}"
POSTGRES_DB="${POSTGRES_DB:-dynflow_test}"

# Container images
POSTGRES_IMAGE="${POSTGRES_IMAGE:-docker.io/library/postgres:15}"
REDIS_IMAGE="${REDIS_IMAGE:-docker.io/library/redis:7-alpine}"

# Start PostgreSQL container
start_postgres() {
echo "Starting PostgreSQL container: ${POSTGRES_CONTAINER_NAME}" >&2

podman run -d \
--name "${POSTGRES_CONTAINER_NAME}" \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e POSTGRES_DB="${POSTGRES_DB}" \
-p "${POSTGRES_PORT}:5432" \
"${POSTGRES_IMAGE}" \
postgres -c fsync=off -c synchronous_commit=off -c full_page_writes=off

# Wait for PostgreSQL to be ready
echo "Waiting for PostgreSQL to be ready..." >&2
local max_attempts=30
local attempt=0

while [ $attempt -lt $max_attempts ]; do
if podman exec "${POSTGRES_CONTAINER_NAME}" pg_isready -U "${POSTGRES_USER}" > /dev/null 2>&1; then
echo "PostgreSQL is ready" >&2
return 0
fi
attempt=$((attempt + 1))
sleep 1
done

echo "ERROR: PostgreSQL failed to start within ${max_attempts} seconds" >&2
return 1
}

stop_container() {
local container="$1"
local with_volumes="$2"

echo "Stopping container: ${container}" >&2
if podman ps -a --format "{{.Names}}" | grep -q "^${container}$"; then
podman stop -t 2 "${container}" > /dev/null 2>&1 || true
if [ "$with_volumes" = "1" ]; then
podman rm -v -f "${container}" > /dev/null 2>&1 || true
else
podman rm -f "${container}" > /dev/null 2>&1 || true
fi
fi
}

# Stop PostgreSQL container
stop_postgres() {
stop_container "$POSTGRES_CONTAINER_NAME" "$1"
}

# Start Redis container
start_redis() {
echo "Starting Redis container: ${REDIS_CONTAINER_NAME}" >&2

podman run -d \
--name "${REDIS_CONTAINER_NAME}" \
-p "${REDIS_PORT}:6379" \
"${REDIS_IMAGE}"

# Wait for Redis to be ready
echo "Waiting for Redis to be ready..." >&2
local max_attempts=30
local attempt=0

while [ $attempt -lt $max_attempts ]; do
if podman exec "${REDIS_CONTAINER_NAME}" redis-cli ping > /dev/null 2>&1; then
echo "Redis is ready" >&2
return 0
fi
attempt=$((attempt + 1))
sleep 1
done

echo "ERROR: Redis failed to start within ${max_attempts} seconds" >&2
return 1
}

# Stop Redis container
stop_redis() {
stop_container "$REDIS_CONTAINER_NAME" "$1"
}

# Check if PostgreSQL container is running
is_postgres_running() {
podman ps --format "{{.Names}}" | grep -q "^${POSTGRES_CONTAINER_NAME}$"
}

# Check if Redis container is running
is_redis_running() {
podman ps --format "{{.Names}}" | grep -q "^${REDIS_CONTAINER_NAME}$"
}

# Get PostgreSQL connection string
get_postgres_url() {
echo "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}"
}

# Get Redis URL
get_redis_url() {
echo "redis://localhost:${REDIS_PORT}/0"
}

# Execute SQL in PostgreSQL container
exec_sql() {
local sql="$1"
podman exec "${POSTGRES_CONTAINER_NAME}" \
psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "${sql}"
}

# Execute Redis command
exec_redis() {
podman exec "${REDIS_CONTAINER_NAME}" redis-cli "$@"
}

# Clean up all test containers
cleanup_containers() {
stop_postgres 1
stop_redis 1
}

# Start all test containers
start_containers() {
start_postgres || return 1
start_redis || return 1
}
46 changes: 46 additions & 0 deletions test/bats/setup_suite.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Suite-level setup - runs once before all tests

# Load container helpers
source "$(dirname "${BASH_SOURCE[0]}")/helpers/containers.bash"
source "$(dirname "${BASH_SOURCE[0]}")/helpers/common.bash"

# This function runs once before all tests in the suite
setup_suite() {
echo "=== Setting up bats test suite ===" >&2

# Verify podman is available
if ! command -v podman &> /dev/null; then
echo "ERROR: podman is not installed or not in PATH" >&2
exit 1
fi

# Check if bundle is available
PROJECT_ROOT="$(get_project_root)"
if ! command -v bundle &> /dev/null; then
echo "WARNING: bundler is not installed" >&2
else
# Install dependencies if needed
echo "Checking bundle dependencies..." >&2
cd "${PROJECT_ROOT}" && bundle check > /dev/null 2>&1 || bundle install
fi

# Pull container images if not already present
echo "Checking container images..." >&2

if ! podman image exists "${POSTGRES_IMAGE}"; then
echo "Pulling PostgreSQL image: ${POSTGRES_IMAGE}" >&2
podman pull "${POSTGRES_IMAGE}"
fi

if ! podman image exists "${REDIS_IMAGE}"; then
echo "Pulling Redis image: ${REDIS_IMAGE}" >&2
podman pull "${REDIS_IMAGE}"
fi

# Clean up any existing test containers from previous runs
echo "Cleaning up any existing test containers..." >&2
cleanup_containers

echo "=== Test suite setup complete ===" >&2
}
Loading