Skip to content

Merge pull request #370 from dpw13/fix/audit-strcpy #1074

Merge pull request #370 from dpw13/fix/audit-strcpy

Merge pull request #370 from dpw13/fix/audit-strcpy #1074

name: Integration Tests
on:
push:
branches:
- main
- 'feature/**'
workflow_dispatch:
permissions:
contents: read
checks: write
pull-requests: write
jobs:
unity-tests:
name: Unity C Tests (Layer 1 + Layer 2)
runs-on: ubuntu-latest
container: debian:sid-slim
timeout-minutes: 10
steps:
- name: Install C build dependencies
run: |
apt-get update
apt-get install -y \
git \
build-essential \
cmake \
pkg-config \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libswscale-dev \
libsqlite3-dev \
libuv1-dev \
libllhttp-dev \
libcurl4-openssl-dev \
libcjson-dev \
libmbedtls-dev \
lcov \
curl \
gpg
- name: Checkout
uses: actions/checkout@v5
with:
submodules: false
- name: Build Unity tests
run: |
mkdir -p build
cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DENABLE_SOD=OFF \
-DENABLE_GO2RTC=OFF \
-DENABLE_MQTT=OFF \
-DCMAKE_C_FLAGS="--coverage" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage"
make -j$(nproc) \
test_storage_pressure \
test_storage_pressure_extended \
test_detection_result_structures \
test_storage_retention_sqlite \
test_config \
test_logger \
test_strings \
test_memory \
test_request_response \
test_shutdown_coordinator \
test_detection_config \
test_detection_model_motion \
test_db_streams \
test_db_recordings_extended \
test_db_detections \
test_db_zones \
test_db_events \
test_db_auth \
test_db_transactions \
test_db_maintenance \
test_db_query_builder \
test_logger_json \
test_batch_delete_progress \
test_db_motion_config \
test_db_recordings_sync \
test_httpd_utils \
test_zone_filter \
test_onvif_soap_fault \
test_stream_manager \
test_stream_state \
test_packet_buffer \
test_timestamp_manager
echo "Unity test binaries built:"
ls -la bin/test_*
- name: Run Unity tests
run: |
cd build
ctest --output-on-failure -V \
-R "test_storage_pressure$|test_storage_pressure_extended|test_detection_result_structures|test_storage_retention_sqlite|test_config|test_logger|test_strings|test_memory|test_request_response|test_shutdown_coordinator|test_detection_config|test_detection_model_motion|test_db_streams|test_db_recordings_extended|test_db_detections|test_db_zones|test_db_events|test_db_auth|test_db_transactions|test_db_maintenance|test_db_query_builder|test_logger_json|test_batch_delete_progress|test_db_motion_config|test_db_recordings_sync|test_httpd_utils|test_zone_filter|test_onvif_soap_fault|test_stream_manager|test_stream_state|test_packet_buffer|test_timestamp_manager"
- name: Generate coverage report
if: always()
run: |
cd build
# Collect coverage data from all .gcda files generated during the test run
lcov --capture \
--directory . \
--output-file coverage.info \
--ignore-errors mismatch
# Strip noise: system headers, third-party code, and the test files themselves
lcov --remove coverage.info \
'/usr/*' \
'*/third_party/*' \
'*/external/*' \
'*/tests/*' \
--output-file coverage_filtered.info \
--ignore-errors unused
# Print a quick per-file summary to the job log
lcov --list coverage_filtered.info
# Render HTML
genhtml coverage_filtered.info \
--output-directory ../coverage-report \
--title "LightNVR C Unit-Test Coverage" \
--show-details \
--legend
echo "Coverage report generated at coverage-report/"
- name: Upload coverage report
uses: actions/upload-artifact@v6
if: always()
with:
name: c-coverage-report
path: coverage-report/
retention-days: 30
- name: Upload to Codecov
uses: codecov/codecov-action@v5
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverage_filtered.info
flags: unity-c
name: lightnvr-c-coverage
fail_ci_if_error: false
- name: Upload test results on failure
uses: actions/upload-artifact@v6
if: failure()
with:
name: unity-test-results
path: build/Testing/
retention-days: 7
playwright-tests:
name: Playwright Integration Tests (Layer 3)
needs: unity-tests
runs-on: ubuntu-latest
container: debian:sid-slim
timeout-minutes: 30
steps:
- name: Install system dependencies
run: |
apt-get update
apt-get install -y \
git \
build-essential \
cmake \
pkg-config \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libswscale-dev \
libsqlite3-dev \
libuv1-dev \
libllhttp-dev \
libcurl4-openssl-dev \
libcjson-dev \
libmbedtls-dev \
ffmpeg \
curl \
jq \
golang-go \
nodejs \
npm \
procps
- name: Checkout
uses: actions/checkout@v5
with:
submodules: recursive
- name: Build go2rtc from source
run: |
# Build go2rtc from the opensensor fork submodule
cd go2rtc
echo "Building go2rtc from source..."
CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -w" -trimpath -o go2rtc
chmod +x go2rtc
./go2rtc --version || echo "go2rtc built successfully"
echo "go2rtc binary size: $(stat -c%s go2rtc) bytes"
# Binary is now at go2rtc/go2rtc - exactly where lightnvr-test.ini expects it
- name: Build lightNVR
run: |
mkdir -p build
cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_GO2RTC=ON \
-DGO2RTC_BINARY_PATH=$(pwd)/../go2rtc/go2rtc
make -j$(nproc)
echo "lightNVR built successfully"
ls -la bin/lightnvr
- name: Build web frontend
run: |
cd web
npm ci
npm run build
echo "Web frontend built successfully"
ls -la dist/ | head -10
- name: Install Playwright dependencies
run: |
npm ci
npx playwright install --with-deps chromium
- name: Run Playwright UI tests
run: |
# Clean any existing playwright reports to ensure fresh state
rm -rf playwright-report test-results
mkdir -p playwright-report test-results
# Pre-flight checks before running tests
echo "=== Pre-flight checks ==="
echo "Checking if lightNVR binary exists..."
ls -la build/bin/lightnvr || echo "lightNVR binary NOT found"
echo ""
echo "Checking if go2rtc binary exists..."
ls -la go2rtc/go2rtc || echo "go2rtc binary NOT found"
echo ""
echo "Checking if web/dist exists..."
ls -la web/dist/ | head -5 || echo "web/dist NOT found"
echo ""
echo "Checking if config file exists..."
ls -la config/lightnvr-test.ini || echo "Config file NOT found"
echo ""
# Try to manually start lightNVR to see if it works
echo "=== Attempting manual lightNVR start for diagnostics ==="
mkdir -p /tmp/lightnvr-test/recordings /tmp/lightnvr-test/go2rtc
timeout 10 ./build/bin/lightnvr -c config/lightnvr-test.ini &
MANUAL_PID=$!
echo "Started lightNVR manually with PID: $MANUAL_PID"
sleep 5
# Check if it's still running
if kill -0 $MANUAL_PID 2>/dev/null; then
echo "lightNVR is running (PID: $MANUAL_PID)"
# Check if it's listening on port 18080
if curl -s -u admin:admin http://localhost:18080/api/system > /dev/null 2>&1; then
echo "lightNVR is responding on port 18080"
else
echo "lightNVR is NOT responding on port 18080"
netstat -tlpn | grep 18080 || echo "Port 18080 is not listening"
fi
else
echo "lightNVR process exited"
wait $MANUAL_PID
echo "Exit code: $?"
fi
# Check logs
if [ -f /tmp/lightnvr-test/lightnvr.log ]; then
echo "=== lightNVR log (last 50 lines) ==="
tail -50 /tmp/lightnvr-test/lightnvr.log
else
echo "No log file at /tmp/lightnvr-test/lightnvr.log"
fi
# Kill the manual instance and ALL child processes it spawned
# (lightNVR starts go2rtc as a child; killing only $MANUAL_PID leaves
# go2rtc alive on port 11984, causing playwright's lightNVR to fail
# with "Cannot start go2rtc because port 11984 is already in use")
kill $MANUAL_PID 2>/dev/null || true
pkill -TERM -f lightnvr 2>/dev/null || true
pkill -TERM -f go2rtc 2>/dev/null || true
sleep 3
pkill -KILL -f lightnvr 2>/dev/null || true
pkill -KILL -f go2rtc 2>/dev/null || true
sleep 1
# Clean up for actual test run
rm -rf /tmp/lightnvr-test
echo ""
echo "=== Running Playwright tests ==="
# Run Playwright tests with output capture.
# Avoid ${PIPESTATUS[0]} — it is bash-only and the container
# shell is /bin/sh (dash). Capture the exit code directly.
RESULT=0
npx playwright test --project=ui > playwright-output.log 2>&1 || RESULT=$?
cat playwright-output.log
if [ $RESULT -ne 0 ]; then
echo ""
echo "=== Playwright tests failed ==="
echo ""
echo "Checking if lightNVR is still running..."
pgrep -a lightnvr || echo "lightNVR is NOT running"
pgrep -a go2rtc || echo "go2rtc is NOT running"
echo ""
echo "Checking lightNVR logs..."
if [ -f /tmp/lightnvr-test/lightnvr.log ]; then
echo "=== Last 200 lines of lightnvr.log ==="
tail -200 /tmp/lightnvr-test/lightnvr.log
else
echo "No log file found at /tmp/lightnvr-test/lightnvr.log"
fi
echo ""
echo "Checking for core dumps..."
ls -la /tmp/core* 2>/dev/null || echo "No core dumps found"
echo ""
exit $RESULT
fi
env:
CI: true
- name: Upload Playwright report
uses: actions/upload-artifact@v6
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- name: Upload test screenshots
uses: actions/upload-artifact@v6
if: failure()
with:
name: test-screenshots
path: test-results/
retention-days: 7
- name: Upload lightNVR logs
uses: actions/upload-artifact@v6
if: failure()
with:
name: lightnvr-logs
path: |
/tmp/lightnvr-test/
playwright-output.log
retention-days: 7
- name: Cleanup
if: always()
run: |
# Stop any remaining processes
pkill -f lightnvr || true
pkill -f go2rtc || true
# Clean up test artifacts
rm -rf /tmp/lightnvr-test