diff --git a/.github/workflows/oss-integration.yaml b/.github/workflows/oss-integration.yaml new file mode 100644 index 000000000..e6dbb5fec --- /dev/null +++ b/.github/workflows/oss-integration.yaml @@ -0,0 +1,47 @@ +--- +name: integration-test +description: Run integration test for ivi-homescreen + +on: + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +jobs: + # Runs integration test for the CrashHandler (Sentry integration) + # - Sets up a local Kent (fake Sentry) server + # - Builds a homescreen binary with the crash handler and integration test enabled + # - Runs the binary, which intentionally crashes; checks if the crash report is received + crash-handler: + if: ${{ github.server_url == 'https://github.com' && github.ref != 'refs/heads/agl' }} + runs-on: ubuntu-22.04 + env: + PREFER_LLVM: 19 + steps: + - name: Check Environment + id: check_environment + shell: bash + run: | + echo "Running on Ubuntu version: $(lsb_release -d | cut -f2)" + echo "Running on architecture: $(uname -m)" + echo "Current directory: $(pwd)" + echo "user=$(whoami)" >> "$GITHUB_OUTPUT" + + - name: Setup workspace + uses: meta-flutter/workspace-automation/.github/actions/setup-workspace@v2.0 + with: + args: --disable=rive-text --enable=sentry-native --override-repo=https://github.com/toyota-connected/ivi-homescreen.git#${{ github.event.pull_request.head.ref && format('heads/{0}', github.event.pull_request.head.ref) || github.sha }} + user: ${{ steps.check_environment.outputs.user }} + ref: v2.0 + cache: false + cache-key: crash-handler-integration-pr${{ github.event.pull_request.number }} + + - name: Run integration test for crash handler + shell: bash + run: | + cd "${{ github.workspace }}" + source ./setup_env.sh + cd ./app/ivi-homescreen + ls -al scripts + git status + ./scripts/crash_handler_integration.sh diff --git a/cmake/config_common.h.in b/cmake/config_common.h.in index 1b5d2de12..107fc3137 100644 --- a/cmake/config_common.h.in +++ b/cmake/config_common.h.in @@ -74,6 +74,7 @@ static constexpr char kSentryEnvDefault[] = "@CMAKE_BUILD_TYPE@"; static constexpr char kCrashpadBinaryPath[] = "@CRASHPAD_BINARY_DIR@/crashpad_handler"; #endif +#cmakedefine01 INTEGRATION_TEST_CRASH_HANDLER #cmakedefine01 BUILD_ACCESSIBILITY diff --git a/cmake/options.cmake b/cmake/options.cmake index 488a90ecc..e85fc3fc0 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -248,6 +248,13 @@ else() message(STATUS "Crash Handler .......... Disabled") endif () +option(INTEGRATION_TEST_CRASH_HANDLER "Enable crash handler integration test" OFF) +if (INTEGRATION_TEST_CRASH_HANDLER) + message(STATUS "Crash Handler Integration Test ... Enabled") +else() + message(STATUS "Crash Handler Integration Test ... Disabled") +endif () + # # watchdog # diff --git a/scripts/crash_handler_integration.sh b/scripts/crash_handler_integration.sh new file mode 100755 index 000000000..8d36f9078 --- /dev/null +++ b/scripts/crash_handler_integration.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Crash-handler integration test — mirrors the crash-handler job in +# .github/workflows/oss-integration.yaml for local / Docker reproduction. +# +# Intended to be run inside a Docker container via: +# /home/jamie/workspace-automation/run-docker.sh ubuntu:22.04 amd64 \ +# "scripts/crash_handler_integration.sh" +# +# Prerequisites (handled by flutter_workspace.py before this script runs): +# - sentry-native built under ${FLUTTER_WORKSPACE}/app/sentry-native/ +# - ivi-homescreen sources at ${FLUTTER_WORKSPACE}/app/ivi-homescreen/ +# - material_3_demo at ${FLUTTER_WORKSPACE}/app/samples/material_3_demo/ +# +# Environment variables (all optional): +# KENT_HOST — host for the fake Sentry server (default: 127.0.0.1) +# KENT_PORT — port for the fake Sentry server (default: 5000) + +set -euo pipefail + +KENT_HOST="${KENT_HOST:-127.0.0.1}" +KENT_PORT="${KENT_PORT:-5000}" + +IVI_SRC="${FLUTTER_WORKSPACE}/app/ivi-homescreen" +IVI_BUILD="${IVI_SRC}/build" +SENTRY_LIBDIR="${FLUTTER_WORKSPACE}/app/sentry-native/build/release/staging" +CRASHPAD_BINDIR="${FLUTTER_WORKSPACE}/app/sentry-native/build/release/crashpad_build/handler" +SAMPLES_DIR="${FLUTTER_WORKSPACE}/app/flutter-samples/material_3_demo" +BUNDLE_DIR="${SAMPLES_DIR}/.desktop-homescreen" + +KENT_PID="" + +cleanup() { + if [[ -n "${KENT_PID}" ]]; then + kill "${KENT_PID}" 2>/dev/null || true + fi +} +trap cleanup EXIT + +_dir=$(pwd) + +# --------------------------------------------------------------------------- +# Prepare test app +# --------------------------------------------------------------------------- +cd "${SAMPLES_DIR}" +# this is required to make sure a `.desktop-homescreen` bundle is generated +flutter pub get +flutter build bundle +flutter install -d desktop-homescreen +cd "${_dir}" + +# --------------------------------------------------------------------------- +# Configure +# --------------------------------------------------------------------------- + +cmake \ + -S "${IVI_SRC}" \ + -B "${IVI_BUILD}" \ + -D BUILD_BACKEND_WAYLAND_EGL=OFF \ + -D BUILD_BACKEND_WAYLAND_VULKAN=OFF \ + -D BUILD_BACKEND_DRM_KMS_EGL=OFF \ + -D BUILD_BACKEND_SOFTWARE=ON \ + -D BUILD_CRASH_HANDLER=ON \ + -D INTEGRATION_TEST_CRASH_HANDLER=ON + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- +ninja -C "${IVI_BUILD}" + +# --------------------------------------------------------------------------- +# Install and start Kent (fake Sentry server) +# --------------------------------------------------------------------------- +pip install kent + +kent-server run --host "${KENT_HOST}" --port "${KENT_PORT}" & +KENT_PID=$! +sleep 2 + +run_and_verify_crash() { + # --------------------------------------------------------------------------- + # Run homescreen (expected to crash) + # --------------------------------------------------------------------------- + set +e + SENTRY_DSN="http://public@${KENT_HOST}:${KENT_PORT}/1" \ + "${IVI_BUILD}/shell/homescreen" -b "${BUNDLE_DIR}" + HOMESCREEN_EXIT_CODE=$? + set -e + + if [[ "${HOMESCREEN_EXIT_CODE}" == "0" ]]; then + echo "error: homescreen exited with 0 — expected a crash (non-zero exit)" >&2 + return 1 + fi + + _expected_exit_code=139 + if [[ "${HOMESCREEN_EXIT_CODE}" != "${_expected_exit_code}" ]]; then + echo "error: homescreen exited with ${HOMESCREEN_EXIT_CODE} — expected ${_expected_exit_code}" >&2 + return 1 + else + echo "homescreen exited with expected crash code ${_expected_exit_code}" + fi + + # --------------------------------------------------------------------------- + # Wait for crash report delivery, then verify Kent received it + # --------------------------------------------------------------------------- + sleep 10 + + response=$(curl -sf "http://${KENT_HOST}:${KENT_PORT}/api/eventlist/") + echo "Kent event list: ${response}" + # response = { "events" : [ { "event_id" : "..." }, ... ] } + # use jq to get length of events array + event_count=$(echo "${response}" | jq '.events | length' || echo "0") + if [[ "${event_count}" == "0" ]]; then + echo "error: no crash reports received by Kent" >&2 + return 1 + fi + echo "Kent received ${event_count} crash report(s) as expected" +} + +for attempt in 1 2; do + if run_and_verify_crash; then + break + fi + if [[ "${attempt}" == "2" ]]; then + echo "error: crash handler verification failed after ${attempt} attempts" >&2 + exit 1 + fi + echo "Attempt ${attempt} failed, retrying..." +done \ No newline at end of file diff --git a/shell/crash_handler.cc b/shell/crash_handler.cc index 8bdb3f4c5..8c87ea9bf 100644 --- a/shell/crash_handler.cc +++ b/shell/crash_handler.cc @@ -12,11 +12,13 @@ #include "sentry.h" +#if INTEGRATION_TEST_CRASH_HANDLER volatile auto invalid_mem = reinterpret_cast(1); void CrashHandler::trigger_crash() { memset(invalid_mem, 1, 100); } +#endif CrashHandler::SentryConfig CrashHandler::LoadConfig( const std::string& config_path) { diff --git a/shell/crash_handler.h b/shell/crash_handler.h index ab6b42598..32e064a53 100644 --- a/shell/crash_handler.h +++ b/shell/crash_handler.h @@ -1,9 +1,10 @@ #pragma once +#include + #include #include #include -#include "sentry.h" class CrashHandler { public: @@ -14,7 +15,9 @@ class CrashHandler { ~CrashHandler(); +#if INTEGRATION_TEST_CRASH_HANDLER static void trigger_crash(); +#endif CrashHandler(const CrashHandler&) = delete; CrashHandler& operator=(const CrashHandler&) = delete; diff --git a/shell/main.cc b/shell/main.cc index b50705dc7..4530e2070 100644 --- a/shell/main.cc +++ b/shell/main.cc @@ -93,6 +93,23 @@ int main(const int argc, char** argv) { auto crash_handler = std::make_unique(sentry_config_path); #endif +#if INTEGRATION_TEST_CRASH_HANDLER + // If we're running the crash handler integration test, trigger a crash and + // exit immediately. The test runner will then check if the crash was captured + // successfully. + + spdlog::info( + "Running crash handler integration test: triggering crash in 3 " + "seconds..."); + std::this_thread::sleep_for(std::chrono::seconds(3)); + + CrashHandler::trigger_crash(); + spdlog::error( + "Crash handler integration test failed: trigger_crash() returned instead " + "of crashing."); + return 231; +#endif + // Handle --drm-list-modes[=] as an early exit so the user doesn't need // to supply a bundle just to inspect modes. It is dispatched to the active // backend's mode lister (DRM lists the scanout connector's modes; software