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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.env.example
docker-compose.yml
Dockerfile
cicd/
.github/
41 changes: 41 additions & 0 deletions .github/workflows/test-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI Pipeline

on:
push:
branches: ["testlink_1_9_20_fixed"]
pull_request:
branches: ["testlink_1_9_20_fixed"]
workflow_dispatch:
inputs:
judge_mode:
description: "Test judge mode"
required: false
default: "simple"
type: choice
options:
- "simple"
- "dual"

jobs:
build:
name: Build Tests
uses: ./.github/workflows/test-suite.yml
with:
suite: build
judge_mode: ${{ inputs.judge_mode || 'simple' }}

integration:
name: Integration Tests
needs: build
uses: ./.github/workflows/test-suite.yml
with:
suite: integration
judge_mode: ${{ inputs.judge_mode || 'simple' }}

e2e:
name: E2E Tests
needs: integration
uses: ./.github/workflows/test-suite.yml
with:
suite: e2e
judge_mode: ${{ inputs.judge_mode || 'simple' }}
117 changes: 117 additions & 0 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Test Suite

on:
workflow_dispatch:
inputs:
suite:
description: "Test suite to run"
required: true
type: choice
options:
- "build"
- "integration"
- "e2e"
- "all"
judge_mode:
description: "Test judge mode"
required: false
default: "simple"
type: choice
options:
- "simple"
- "dual"
judge_model:
description: "LLM model for judging (if dual mode)"
required: false
default: "llama3:8b"
type: string
workflow_call:
inputs:
suite:
description: "Test suite to run"
required: true
type: string
judge_mode:
description: "Test judge mode (simple, dual)"
required: false
default: "simple"
type: string
judge_model:
description: "LLM model for judging"
required: false
default: "llama3:8b"
type: string
outputs:
result:
description: "Test result"
value: ${{ jobs.test.outputs.result }}

jobs:
test:
name: Run ${{ inputs.suite }} Tests
runs-on: ubuntu-latest
outputs:
result: ${{ steps.run-tests.outcome }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install test runner dependencies
run: cd cicd/tests && npm ci

- name: Run tests
id: run-tests
run: |
cd cicd/tests

# Build judge flags based on input
JUDGE_FLAGS=""
if [ "${{ inputs.judge_mode }}" = "simple" ] || [ -z "${{ inputs.judge_mode }}" ]; then
JUDGE_FLAGS="--no-llm"
else
JUDGE_FLAGS="--judge-model ${{ inputs.judge_model || 'llama3:8b' }}"
fi

# Build suite flag
SUITE_FLAG=""
if [ "${{ inputs.suite }}" != "all" ]; then
SUITE_FLAG="--suite ${{ inputs.suite }}"
fi

echo "Suite: ${{ inputs.suite }}"
echo "Judge mode: ${{ inputs.judge_mode || 'simple' }}"
echo "Judge flags: $JUDGE_FLAGS"

# Run tests with JSON output
npx tsx src/cli.ts run $SUITE_FLAG $JUDGE_FLAGS --format json > /tmp/test-results.json || true

echo "--- JSON Results ---"
cat /tmp/test-results.json

- name: Check test results
run: |
if [ ! -f /tmp/test-results.json ] || [ ! -s /tmp/test-results.json ]; then
echo "::error::No test results found"
exit 1
fi
FAILED=$(jq '.summary.failed' /tmp/test-results.json)
echo "Failed tests: $FAILED"
if [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED test(s) failed"
exit 1
fi

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ inputs.suite }}-test-results
path: |
/tmp/test-results.json
cicd/results/
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ config_db.inc.php
**/._*
upload_area/**
.env
cicd/tests/node_modules/
cicd/results/
47 changes: 47 additions & 0 deletions cicd/docker-compose.ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Docker Compose for CI testing
# Spins up TestLink + PostgreSQL with auto-initialization
#
# Usage:
# docker compose -f cicd/docker-compose.ci.yml up -d
# docker compose -f cicd/docker-compose.ci.yml down -v

networks:
testlink-ci:
name: testlink-ci

services:
db:
image: postgres:9.6
networks:
- testlink-ci
environment:
POSTGRES_USER: testlink
POSTGRES_PASSWORD: testlink
POSTGRES_DB: testlink
healthcheck:
test: ["CMD-SHELL", "pg_isready -U testlink"]
interval: 5s
timeout: 5s
retries: 10

app:
build: ..
networks:
- testlink-ci
depends_on:
db:
condition: service_healthy
ports:
- "8090:80"
volumes:
- ../logs:/var/testlink/logs:Z
- ../upload_area:/var/testlink/upload_area:Z
- ./scripts/ci-config_db.inc.php:/var/www/html/config_db.inc.php:ro
- ./scripts/ci-custom_config.inc.php:/var/www/html/custom_config.inc.php:ro
- ./scripts/init-db.sh:/var/www/html/cicd/scripts/init-db.sh:ro
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost/login.php || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
2 changes: 2 additions & 0 deletions cicd/results/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
9 changes: 9 additions & 0 deletions cicd/scripts/ci-config_db.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
// CI database configuration for TestLink
// Used by docker-compose.ci.yml
define('DB_TYPE', 'postgres');
define('DB_USER', 'testlink');
define('DB_PASS', 'testlink');
define('DB_HOST', 'db');
define('DB_NAME', 'testlink');
define('DB_TABLE_PREFIX', '');
9 changes: 9 additions & 0 deletions cicd/scripts/ci-custom_config.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
// CI custom configuration for TestLink
// Enables API and disables email

$tlCfg->api->enabled = TRUE;

// Disable email in CI
$g_smtp_host = '';
$tlCfg->smtp_host = '';
10 changes: 10 additions & 0 deletions cicd/scripts/ci-down.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
# Tear down CI environment
set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COMPOSE_FILE="$SCRIPT_DIR/../docker-compose.ci.yml"

echo "=== Stopping TestLink CI Environment ==="
docker compose -f "$COMPOSE_FILE" down -v --remove-orphans
echo "=== CI Environment Stopped ==="
36 changes: 36 additions & 0 deletions cicd/scripts/ci-up.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# Start CI environment: build image, start services, init DB, verify health
set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COMPOSE_FILE="$SCRIPT_DIR/../docker-compose.ci.yml"

echo "=== Starting TestLink CI Environment ==="

echo "Building Docker image..."
docker compose -f "$COMPOSE_FILE" build

echo "Starting services..."
docker compose -f "$COMPOSE_FILE" up -d

echo "Waiting for app to be healthy..."
for i in $(seq 1 60); do
if curl -sf http://localhost:8090/login.php > /dev/null 2>&1; then
echo "App is responding."
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout waiting for app."
docker compose -f "$COMPOSE_FILE" logs app
exit 1
fi
sleep 2
done

echo "Initializing database..."
docker compose -f "$COMPOSE_FILE" exec -T app bash /var/www/html/cicd/scripts/init-db.sh

echo "=== CI Environment Ready ==="
echo "TestLink URL: http://localhost:8090"
echo "Admin user: admin / admin"
echo "API key: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
55 changes: 55 additions & 0 deletions cicd/scripts/format-results.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash
# format-results.sh - Extract and format test results from JSON files
# Converts escaped \n in stdout/stderr to actual newlines for readability

set -e

usage() {
echo "Usage: $0 <json-file> [output-file]"
echo ""
echo "Extracts test results from JSON and formats stdout/stderr with actual newlines."
echo "If output-file is not specified, prints to stdout."
echo ""
echo "Examples:"
echo " $0 build-results.json"
echo " $0 build-results.json build-results.txt"
exit 1
}

if [ -z "$1" ]; then
usage
fi

if [ ! -f "$1" ]; then
echo "Error: File '$1' not found"
exit 1
fi

INPUT_FILE="$1"
OUTPUT_FILE="$2"

# jq filter to format the results
JQ_FILTER='
.results[] |
"================================================================================",
"TEST: \(.testId // .test_id) - \(.name)",
"Suite: \(.suite) | Pass: \(.pass) | Duration: \(.duration)ms",
"Reason: \(.reason // "N/A")",
"================================================================================",
(.steps // [] | .[] |
"",
"--- Step: \(.name) ---",
"Command: \(.command)",
"Exit Code: \(.exitCode) | Pass: \(.pass) | Duration: \(.duration)ms",
(if .stdout and .stdout != "" then "STDOUT:\n\(.stdout)" else empty end),
(if .stderr and .stderr != "" then "STDERR:\n\(.stderr)" else empty end)
),
""
'

if [ -n "$OUTPUT_FILE" ]; then
jq -r "$JQ_FILTER" "$INPUT_FILE" > "$OUTPUT_FILE"
echo "Formatted output written to: $OUTPUT_FILE"
else
jq -r "$JQ_FILTER" "$INPUT_FILE"
fi
44 changes: 44 additions & 0 deletions cicd/scripts/init-db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
# Initialize TestLink database schema for CI
# Runs inside the app container against the CI PostgreSQL instance
set -e

DB_HOST="${DB_HOST:-db}"
DB_USER="${DB_USER:-testlink}"
DB_PASS="${DB_PASS:-testlink}"
DB_NAME="${DB_NAME:-testlink}"

export PGPASSWORD="$DB_PASS"

echo "Waiting for PostgreSQL..."
for i in $(seq 1 30); do
if pg_isready -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q 2>/dev/null; then
echo "PostgreSQL is ready."
break
fi
sleep 1
done

SQL_DIR="/var/www/html/install/sql/postgres"

echo "Creating tables..."
sed 's|/\*prefix\*/||g' "$SQL_DIR/testlink_create_tables.sql" | \
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q

echo "Creating UDFs..."
sed 's|/\*prefix\*/||g' "$SQL_DIR/testlink_create_udf0.sql" | \
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q

echo "Inserting default data..."
sed 's|/\*prefix\*/||g' "$SQL_DIR/testlink_create_default_data.sql" | \
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q

# Generate an API key for the admin user (user_id=1)
# The key is a known value so test cases can use it
API_KEY="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
echo "Setting admin API key..."
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -q -c \
"UPDATE users SET script_key='$API_KEY' WHERE id=1;"

echo "Database initialized successfully."
echo "Admin API key: $API_KEY"
Loading
Loading