From 63604b09f2af9489d8d08277d53fd4c70e9e4afb Mon Sep 17 00:00:00 2001 From: bobra200 Date: Tue, 26 May 2026 14:55:21 -0700 Subject: [PATCH 01/15] feat: actions.intent() and related eventing --- README.md | 24 +++++ build.sh | 9 +- include/firebolt/actions.h | 34 +++++++ include/firebolt/firebolt.h | 8 ++ lint.sh | 195 ++++++++++++++++++++++++++++++++++++ run-all-test.sh | 151 ++++++++++++++++++++++++++++ run-component-tests.sh | 98 ++++++++++++++++++ run-unit-tests.sh | 98 ++++++++++++++++++ setup-device-tunnel.sh | 119 ++++++++++++++++++++++ src/actions_impl.cpp | 39 ++++++++ src/actions_impl.h | 33 ++++++ src/firebolt.cpp | 5 + src/json_types/actions.h | 17 ++++ test/CMakeLists.txt | 2 +- test/ComponentTestsMain.cpp | 5 +- test/unit/actionsTest.cpp | 60 +++++++++++ 16 files changed, 892 insertions(+), 5 deletions(-) create mode 100644 include/firebolt/actions.h create mode 100755 lint.sh create mode 100755 run-all-test.sh create mode 100755 run-component-tests.sh create mode 100755 run-unit-tests.sh create mode 100755 setup-device-tunnel.sh create mode 100644 src/actions_impl.cpp create mode 100644 src/actions_impl.h create mode 100644 src/json_types/actions.h create mode 100644 test/unit/actionsTest.cpp diff --git a/README.md b/README.md index 5af09fa..b6289a6 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,27 @@ Implementation of Firebolt C++ Client. For usage instructions of the API test application, see: - [test/api_test_app/README.md](test/api_test_app/README.md) + +## Test Runner + +Use `run-all-test.sh` to build and run unit/component tests locally. + +Examples: + +- `./run-all-test.sh` +- `./run-unit-tests.sh --unit-filter "ActionsUTest.*"` +- `./run-component-tests.sh --component-filter "*Localization*"` + +For the device websocket tunnel, use `setup-device-tunnel.sh`. +Before running it, export `DEVICE_SSH_USER`, `DEVICE_SSH_HOST`, and `DEVICE_SSH_PORT`. + +## Lint + +Use `lint.sh` to run local static analysis for C/C++ sources. + +Examples: + +- `./lint.sh` +- `./lint.sh --tidy-only` +- `./lint.sh --tidy-only --fix` +- `./lint.sh --cppcheck-only` diff --git a/build.sh b/build.sh index 3b1f1c7..3482ffd 100755 --- a/build.sh +++ b/build.sh @@ -51,8 +51,12 @@ while [[ ! -z $1 ]]; do esac; shift done -[[ ! -z $SYSROOT_PATH ]] || { echo "SYSROOT_PATH not set" >/dev/stderr; exit 1; } -[[ -e $SYSROOT_PATH ]] || { echo "SYSROOT_PATH not exist ($SYSROOT_PATH)" >/dev/stderr; exit 1; } +if [[ -n "$SYSROOT_PATH" ]]; then + [[ -e "$SYSROOT_PATH" ]] || { echo "SYSROOT_PATH not exist ($SYSROOT_PATH)" >/dev/stderr; exit 1; } + params+=" -DSYSROOT_PATH=$SYSROOT_PATH" +else + echo "SYSROOT_PATH not set; building without SYSROOT_PATH override" +fi $cleanFirst && rm -rf $bdir @@ -61,7 +65,6 @@ if [[ ! -e "$bdir" || -n "$@" ]]; then command -v ccache >/dev/null 2>&1 && params+=" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" cmake -B $bdir \ -DCMAKE_BUILD_TYPE=$buildType \ - -DSYSROOT_PATH=$SYSROOT_PATH \ $params \ "$@" || exit $? fi diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h new file mode 100644 index 0000000..6d047b5 --- /dev/null +++ b/include/firebolt/actions.h @@ -0,0 +1,34 @@ +// ============================================================================ +// AUTO-GENERATED by fb-gen — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_ACTIONS_H +#define FIREBOLT_ACTIONS_H + +#include +#include +#include +#include +#include +#include + +namespace Firebolt::Actions { + +class IActions { +public: + virtual ~IActions() = default; + + virtual Result intent() const = 0; + + virtual Result subscribeOnIntent(std::function&& notification) = 0; + + virtual Result unsubscribe(SubscriptionId id) = 0; + virtual void unsubscribeAll() = 0; + + // Factory + static IActions* create(); + +}; // class IActions + +} // namespace Firebolt::Actions + +#endif // FIREBOLT_ACTIONS_H diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index f51a7fd..003414e 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -20,6 +20,7 @@ #include "firebolt/accessibility.h" #include "firebolt/advertising.h" +#include "firebolt/actions.h" #include "firebolt/client_export.h" #include "firebolt/device.h" #include "firebolt/discovery.h" @@ -92,6 +93,13 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor */ virtual Advertising::IAdvertising& AdvertisingInterface() = 0; + /** + * @brief Returns instance of Actions interface + * + * @return Reference to Actions interface + */ + virtual Actions::IActions& ActionsInterface() = 0; + /** * @brief Returns instance of Device interface * diff --git a/lint.sh b/lint.sh new file mode 100755 index 0000000..f0b4768 --- /dev/null +++ b/lint.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash + +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="build-dev" +NO_BUILD=false +CLEAN=false +RUN_CLANG_TIDY=true +RUN_CPPCHECK=true +APPLY_FIXES=false +CLANG_TIDY_PATHS=(src include test/unit test/component) + +usage() { + cat < Build directory containing compile_commands.json (default: build-dev) + --tidy-path

Add path for clang-tidy scan (repeatable) + --fix Apply clang-tidy fix-its (clang-tidy only) + --tidy-only Run clang-tidy only + --cppcheck-only Run cppcheck only + --help Show this help + +Examples: + ./lint.sh + ./lint.sh --tidy-only + ./lint.sh --tidy-only --fix + ./lint.sh --tidy-path test/api_test_app + ./lint.sh --no-build --build-dir build-dev +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --clean) + CLEAN=true + ;; + --no-build) + NO_BUILD=true + ;; + --build-dir) + BUILD_DIR="${2:-}" + shift + ;; + --tidy-path) + CLANG_TIDY_PATHS+=("${2:-}") + shift + ;; + --fix) + APPLY_FIXES=true + ;; + --tidy-only) + RUN_CLANG_TIDY=true + RUN_CPPCHECK=false + ;; + --cppcheck-only) + RUN_CLANG_TIDY=false + RUN_CPPCHECK=true + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +cd "$ROOT_DIR" + +if [[ "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then + echo "Nothing to run: both clang-tidy and cppcheck are disabled." >&2 + exit 1 +fi + +if [[ "$APPLY_FIXES" == true && "$RUN_CLANG_TIDY" == false ]]; then + echo "--fix requires clang-tidy to be enabled (remove --cppcheck-only)." >&2 + exit 1 +fi + +if [[ "$RUN_CLANG_TIDY" == true ]] && ! command -v clang-tidy >/dev/null 2>&1; then + echo "clang-tidy not found. Install it (e.g. apt install clang-tidy)." >&2 + exit 1 +fi + +if [[ "$RUN_CPPCHECK" == true ]] && ! command -v cppcheck >/dev/null 2>&1; then + echo "cppcheck not found. Install it (e.g. apt install cppcheck)." >&2 + exit 1 +fi + +if [[ "$CLEAN" == true ]]; then + rm -rf "$BUILD_DIR" +fi + +if [[ "$NO_BUILD" == false ]]; then + ./build.sh +tests +fi + +if [[ ! -f "$BUILD_DIR/compile_commands.json" ]]; then + echo "Missing $BUILD_DIR/compile_commands.json. Run ./build.sh +tests first." >&2 + exit 1 +fi + +if [[ "$RUN_CLANG_TIDY" == true ]]; then + if [[ "$APPLY_FIXES" == true ]]; then + echo "[lint] Running clang-tidy with fixes enabled" + else + echo "[lint] Running clang-tidy" + fi + + existing_paths=() + for p in "${CLANG_TIDY_PATHS[@]}"; do + if [[ -e "$p" ]]; then + existing_paths+=("$p") + fi + done + + if [[ ${#existing_paths[@]} -eq 0 ]]; then + echo "No valid clang-tidy paths found." >&2 + exit 1 + fi + + mapfile -t source_files < <( + find "${existing_paths[@]}" -type f \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.cxx" \) | sort + ) + + if [[ ${#source_files[@]} -eq 0 ]]; then + echo "No C/C++ source files found for clang-tidy." >&2 + exit 1 + fi + + clang_tidy_failed=0 + total_files=${#source_files[@]} + index=0 + for f in "${source_files[@]}"; do + index=$((index + 1)) + echo "[lint][clang-tidy] ${index}/${total_files}: $f" + clang_tidy_cmd=(clang-tidy -p "$BUILD_DIR") + if [[ "$APPLY_FIXES" == true ]]; then + clang_tidy_cmd+=("-fix") + fi + clang_tidy_cmd+=("$f") + if ! "${clang_tidy_cmd[@]}"; then + clang_tidy_failed=1 + fi + done + + if [[ $clang_tidy_failed -ne 0 ]]; then + echo "clang-tidy reported issues." >&2 + exit 1 + fi +fi + +if [[ "$RUN_CPPCHECK" == true ]]; then + echo "[lint] Running cppcheck" + cppcheck \ + --enable=warning,style,performance,portability \ + --std=c++17 \ + --language=c++ \ + --inline-suppr \ + --error-exitcode=1 \ + -I include \ + -I src \ + src include test +fi + +echo "[lint] Completed successfully" diff --git a/run-all-test.sh b/run-all-test.sh new file mode 100755 index 0000000..028d7f9 --- /dev/null +++ b/run-all-test.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash + +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN_UNIT=true +RUN_COMPONENT=true +BUILD_ONLY=false +CLEAN=false +UNIT_FILTER="" +COMPONENT_FILTER="" + +usage() { + cat < GTest filter passed to utApp + --component-filter GTest filter passed to ctApp + --help Show this help + +Examples: + ./run-all-test.sh + ./run-all-test.sh --unit-only --unit-filter "ActionsUTest.*" + ./run-all-test.sh --component-only --component-filter "*Localization*" +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --clean) + CLEAN=true + ;; + --build-only) + BUILD_ONLY=true + ;; + --unit-only) + RUN_COMPONENT=false + ;; + --component-only) + RUN_UNIT=false + ;; + --unit-filter) + UNIT_FILTER="${2:-}" + shift + ;; + --component-filter) + COMPONENT_FILTER="${2:-}" + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +if [[ "$RUN_UNIT" == false && "$RUN_COMPONENT" == false ]]; then + echo "Nothing to run: both unit and component test execution are disabled." >&2 + exit 1 +fi + +run_unit_script="$ROOT_DIR/run-unit-tests.sh" +run_component_script="$ROOT_DIR/run-component-tests.sh" + +bootstrap_args=() +if [[ "$CLEAN" == true ]]; then + bootstrap_args+=(--clean) +fi + +if [[ "$RUN_UNIT" == true && "$RUN_COMPONENT" == true ]]; then + echo "[run-all-test] Bootstrapping build once via run-unit-tests.sh --build-only" + "$run_unit_script" "${bootstrap_args[@]}" --build-only + + if [[ "$BUILD_ONLY" == true ]]; then + echo "Build complete. Skipping test execution (--build-only)." + exit 0 + fi + + unit_args=(--no-build) + if [[ -n "$UNIT_FILTER" ]]; then + unit_args+=(--unit-filter "$UNIT_FILTER") + fi + echo "[run-all-test] Running unit tests via run-unit-tests.sh" + "$run_unit_script" "${unit_args[@]}" + + component_args=(--no-build) + if [[ -n "$COMPONENT_FILTER" ]]; then + component_args+=(--component-filter "$COMPONENT_FILTER") + fi + echo "[run-all-test] Running component tests via run-component-tests.sh" + "$run_component_script" "${component_args[@]}" + exit 0 +fi + +if [[ "$RUN_UNIT" == true ]]; then + unit_args=() + if [[ "$CLEAN" == true ]]; then + unit_args+=(--clean) + fi + if [[ "$BUILD_ONLY" == true ]]; then + unit_args+=(--build-only) + fi + if [[ -n "$UNIT_FILTER" ]]; then + unit_args+=(--unit-filter "$UNIT_FILTER") + fi + echo "[run-all-test] Delegating to run-unit-tests.sh" + "$run_unit_script" "${unit_args[@]}" + exit 0 +fi + +component_args=() +if [[ "$CLEAN" == true ]]; then + component_args+=(--clean) +fi +if [[ "$BUILD_ONLY" == true ]]; then + component_args+=(--build-only) +fi +if [[ -n "$COMPONENT_FILTER" ]]; then + component_args+=(--component-filter "$COMPONENT_FILTER") +fi +echo "[run-all-test] Delegating to run-component-tests.sh" +"$run_component_script" "${component_args[@]}" diff --git a/run-component-tests.sh b/run-component-tests.sh new file mode 100755 index 0000000..e3162be --- /dev/null +++ b/run-component-tests.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="build-dev" +BUILD_ONLY=false +NO_BUILD=false +CLEAN=false +COMPONENT_FILTER="" + +usage() { + cat < GTest filter passed to ctApp + --help Show this help + +Examples: + ./run-component-tests.sh + FIREBOLT_ENDPOINT=ws://127.0.0.1:3474/ ./run-component-tests.sh --component-filter "*Localization*" +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --clean) + CLEAN=true + ;; + --build-only) + BUILD_ONLY=true + ;; + --no-build) + NO_BUILD=true + ;; + --component-filter) + COMPONENT_FILTER="${2:-}" + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +cd "$ROOT_DIR" + +if [[ "$CLEAN" == true ]]; then + rm -rf "$BUILD_DIR" +fi + +if [[ "$NO_BUILD" == false ]]; then + ./build.sh +tests +fi + +if [[ "$BUILD_ONLY" == true ]]; then + echo "Build complete. Skipping test execution (--build-only)." + exit 0 +fi + +cd "$BUILD_DIR/test" +export LD_LIBRARY_PATH="../src:${LD_LIBRARY_PATH:-}" + +component_cmd=(./ctApp) +if [[ -n "$COMPONENT_FILTER" ]]; then + component_cmd+=("--gtest_filter=$COMPONENT_FILTER") +fi +echo "[run-component-tests] Running component tests: ${component_cmd[*]}" +"${component_cmd[@]}" \ No newline at end of file diff --git a/run-unit-tests.sh b/run-unit-tests.sh new file mode 100755 index 0000000..c2f2706 --- /dev/null +++ b/run-unit-tests.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="build-dev" +BUILD_ONLY=false +NO_BUILD=false +CLEAN=false +UNIT_FILTER="" + +usage() { + cat < GTest filter passed to utApp + --help Show this help + +Examples: + ./run-unit-tests.sh + ./run-unit-tests.sh --unit-filter "ActionsUTest.*" +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --clean) + CLEAN=true + ;; + --build-only) + BUILD_ONLY=true + ;; + --no-build) + NO_BUILD=true + ;; + --unit-filter) + UNIT_FILTER="${2:-}" + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +cd "$ROOT_DIR" + +if [[ "$CLEAN" == true ]]; then + rm -rf "$BUILD_DIR" +fi + +if [[ "$NO_BUILD" == false ]]; then + ./build.sh +tests +fi + +if [[ "$BUILD_ONLY" == true ]]; then + echo "Build complete. Skipping test execution (--build-only)." + exit 0 +fi + +cd "$BUILD_DIR/test" +export LD_LIBRARY_PATH="../src:${LD_LIBRARY_PATH:-}" + +unit_cmd=(./utApp) +if [[ -n "$UNIT_FILTER" ]]; then + unit_cmd+=("--gtest_filter=$UNIT_FILTER") +fi +echo "[run-unit-tests] Running unit tests: ${unit_cmd[*]}" +"${unit_cmd[@]}" \ No newline at end of file diff --git a/setup-device-tunnel.sh b/setup-device-tunnel.sh new file mode 100755 index 0000000..49927a7 --- /dev/null +++ b/setup-device-tunnel.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +LOCAL_PORT="3474" +REMOTE_PORT="3474" +REMOTE_BIND_ADDR="127.0.0.1" +BACKGROUND=false + +require_env() { + local name="$1" + local value="${!name:-}" + if [[ -z "$value" ]]; then + echo "Error: required environment variable $name is not set" >&2 + exit 1 + fi +} + +usage() { + cat < -> : on + +Required environment variables: + DEVICE_SSH_USER SSH username + DEVICE_SSH_HOST Remote SSH host + DEVICE_SSH_PORT SSH port + +Defaults: + local-port ${LOCAL_PORT} + remote-port ${REMOTE_PORT} + remote-bind-addr ${REMOTE_BIND_ADDR} + +Options: + --local-port Local forwarded port (default: ${LOCAL_PORT}) + --remote-port Remote websocket port (default: ${REMOTE_PORT}) + --remote-bind Remote bind address (default: ${REMOTE_BIND_ADDR}) + --background Run ssh tunnel in background + --help Show this help + +Examples: + export DEVICE_SSH_USER=root + export DEVICE_SSH_HOST=192.168.201.170 + export DEVICE_SSH_PORT=10022 + ./setup-device-tunnel.sh + +After tunnel is up, run component tests with: + FIREBOLT_ENDPOINT=ws://127.0.0.1:${LOCAL_PORT}/ ./run-component-tests.sh +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --local-port) + LOCAL_PORT="${2:-}" + shift + ;; + --remote-port) + REMOTE_PORT="${2:-}" + shift + ;; + --remote-bind) + REMOTE_BIND_ADDR="${2:-}" + shift + ;; + --background) + BACKGROUND=true + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +require_env DEVICE_SSH_USER +require_env DEVICE_SSH_HOST +require_env DEVICE_SSH_PORT + +TARGET="${DEVICE_SSH_USER}@${DEVICE_SSH_HOST}" +FORWARD_SPEC="${LOCAL_PORT}:${REMOTE_BIND_ADDR}:${REMOTE_PORT}" +SSH_ARGS=(-N -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ExitOnForwardFailure=yes -p "$DEVICE_SSH_PORT" -L "$FORWARD_SPEC" "$TARGET") + +echo "Opening SSH tunnel: localhost:${LOCAL_PORT} -> ${REMOTE_BIND_ADDR}:${REMOTE_PORT} on ${TARGET}" + +if [[ "$BACKGROUND" == true ]]; then + LOG_FILE="${TMPDIR:-/tmp}/firebolt-tunnel-${LOCAL_PORT}.log" + nohup ssh "${SSH_ARGS[@]}" >"$LOG_FILE" 2>&1 & + SSH_PID=$! + echo "Tunnel PID: ${SSH_PID}" + echo "Tunnel log: ${LOG_FILE}" + exit 0 +fi + +exec ssh "${SSH_ARGS[@]}" diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp new file mode 100644 index 0000000..33d09e4 --- /dev/null +++ b/src/actions_impl.cpp @@ -0,0 +1,39 @@ +// ============================================================================ +// AUTO-GENERATED by fb-gen — DO NOT EDIT +// ============================================================================ +#include "actions_impl.h" +#include "json_types/actions.h" +#include +#include + +namespace Firebolt::Actions { + +ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) + : helper_(helper), + subscriptionManager_(helper, this) +{} + +Result ActionsImpl::intent() const { + return helper_.get("actions.intent"); +} + +Result ActionsImpl::subscribeOnIntent(std::function&& notification) +{ + return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); +} + +Result ActionsImpl::unsubscribe(SubscriptionId id) +{ + return subscriptionManager_.unsubscribe(id); +} + +void ActionsImpl::unsubscribeAll() +{ + subscriptionManager_.unsubscribeAll(); +} + +IActions* IActions::create() { + return new ActionsImpl(Firebolt::Helpers::GetHelperInstance()); +} + +} // namespace Firebolt::Actions diff --git a/src/actions_impl.h b/src/actions_impl.h new file mode 100644 index 0000000..c561970 --- /dev/null +++ b/src/actions_impl.h @@ -0,0 +1,33 @@ +// ============================================================================ +// AUTO-GENERATED by fb-gen — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_ACTIONS_IMPL_H +#define FIREBOLT_ACTIONS_IMPL_H + +#include "firebolt/actions.h" +#include + +namespace Firebolt::Actions { + +class ActionsImpl : public IActions { +public: + explicit ActionsImpl(Firebolt::Helpers::IHelper& helper); + ActionsImpl(const ActionsImpl&) = delete; + ActionsImpl& operator=(const ActionsImpl&) = delete; + ~ActionsImpl() override = default; + + Result intent() const override; + + Result subscribeOnIntent(std::function&& notification) override; + + Result unsubscribe(SubscriptionId id) override; + void unsubscribeAll() override; + +private: + Firebolt::Helpers::IHelper& helper_; + Firebolt::Helpers::SubscriptionManager subscriptionManager_; +}; + +} // namespace Firebolt::Actions + +#endif // FIREBOLT_ACTIONS_IMPL_H diff --git a/src/firebolt.cpp b/src/firebolt.cpp index ba0470c..48ec277 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -19,6 +19,7 @@ #include "firebolt/firebolt.h" #include "accessibility_impl.h" #include "advertising_impl.h" +#include "actions_impl.h" #include "device_impl.h" #include "discovery_impl.h" #include "display_impl.h" @@ -40,6 +41,7 @@ class FireboltAccessorImpl : public IFireboltAccessor FireboltAccessorImpl() : accessibility_(Firebolt::Helpers::GetHelperInstance()), advertising_(Firebolt::Helpers::GetHelperInstance()), + actions_(Firebolt::Helpers::GetHelperInstance()), device_(Firebolt::Helpers::GetHelperInstance()), discovery_(Firebolt::Helpers::GetHelperInstance()), display_(Firebolt::Helpers::GetHelperInstance()), @@ -73,6 +75,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Accessibility::IAccessibility& AccessibilityInterface() override { return accessibility_; } Advertising::IAdvertising& AdvertisingInterface() override { return advertising_; } + Actions::IActions& ActionsInterface() override { return actions_; } Device::IDevice& DeviceInterface() override { return device_; } Discovery::IDiscovery& DiscoveryInterface() override { return discovery_; } Display::IDisplay& DisplayInterface() override { return display_; } @@ -88,6 +91,7 @@ class FireboltAccessorImpl : public IFireboltAccessor void unsubscribeAll() { accessibility_.unsubscribeAll(); + actions_.unsubscribeAll(); lifecycle_.unsubscribeAll(); localization_.unsubscribeAll(); network_.unsubscribeAll(); @@ -98,6 +102,7 @@ class FireboltAccessorImpl : public IFireboltAccessor private: Accessibility::AccessibilityImpl accessibility_; Advertising::AdvertisingImpl advertising_; + Actions::ActionsImpl actions_; Device::DeviceImpl device_; Discovery::DiscoveryImpl discovery_; Display::DisplayImpl display_; diff --git a/src/json_types/actions.h b/src/json_types/actions.h new file mode 100644 index 0000000..e8d71b1 --- /dev/null +++ b/src/json_types/actions.h @@ -0,0 +1,17 @@ +// ============================================================================ +// AUTO-GENERATED by fb-gen — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_ACTIONS_JSON_H +#define FIREBOLT_ACTIONS_JSON_H + +#pragma once +#include +#include +#include +#include "firebolt/actions.h" + +namespace Firebolt::Actions { + +} // namespace Firebolt::Actions + +#endif // FIREBOLT_ACTIONS_JSON_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9b60ab9..a27713b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,7 @@ include(GoogleTest) enable_testing() -add_compile_definitions(UT_OPEN_RPC_FILE="firebolt-open-rpc.json") +add_compile_definitions(UT_OPEN_RPC_FILE="${CMAKE_SOURCE_DIR}/docs/openrpc/the-spec/firebolt-open-rpc.json") set(UNIT_TESTS_APP utApp) diff --git a/test/ComponentTestsMain.cpp b/test/ComponentTestsMain.cpp index 0346a2b..dd58c01 100644 --- a/test/ComponentTestsMain.cpp +++ b/test/ComponentTestsMain.cpp @@ -18,6 +18,7 @@ #include "firebolt/firebolt.h" #include "gtest/gtest.h" +#include #include #include #include @@ -68,7 +69,9 @@ bool waitOnConnectionReady() int main(int argc, char** argv) { - string url = "ws://127.0.0.1:9998/"; + const char* endpoint = std::getenv("FIREBOLT_ENDPOINT"); + string url = (endpoint && endpoint[0] != '\0') ? endpoint : "ws://127.0.0.1:3474/"; + cout << "Using Firebolt endpoint: " << url << endl; createFireboltInstance(url); std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp new file mode 100644 index 0000000..1eafe27 --- /dev/null +++ b/test/unit/actionsTest.cpp @@ -0,0 +1,60 @@ +/** + * Copyright 2025 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "json_engine.h" +#include "mock_helper.h" +#include "actions_impl.h" + +class ActionsUTest : public ::testing::Test, protected MockBase +{ +protected: + Firebolt::Actions::ActionsImpl actionsImpl_{mockHelper}; +}; + +TEST_F(ActionsUTest, Intent) +{ + const std::string expectedValue = "{\"intent\":\"launch\"}"; + mock_with_response("actions.intent", expectedValue); + + auto result = actionsImpl_.intent(); + + ASSERT_TRUE(result) << "ActionsImpl::intent() returned an error"; + EXPECT_EQ(*result, expectedValue); +} + +TEST_F(ActionsUTest, IntentBadResponse) +{ + mock_with_response("actions.intent", true); + + auto result = actionsImpl_.intent(); + + ASSERT_FALSE(result) << "ActionsImpl::intent() did not return an error"; +} + +TEST_F(ActionsUTest, SubscribeOnIntent) +{ + nlohmann::json expectedValue = 1; + mockSubscribe("Actions.onIntent"); + + auto result = actionsImpl_.subscribeOnIntent([&](const std::string& /*value*/) {}); + + ASSERT_TRUE(result) << "ActionsImpl::subscribeOnIntent() returned an error"; + EXPECT_EQ(*result, expectedValue); + + actionsImpl_.unsubscribe(*result); +} From d1c7f4c44e95618a1c501a5f3c5d7f7608675a55 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 09:07:56 -0700 Subject: [PATCH 02/15] fix: copilot feedback on bad accessor --- include/firebolt/actions.h | 8 ++-- src/actions_impl.cpp | 23 ++++------ src/actions_impl.h | 4 +- test/component/actionsGeneratedTest.cpp | 29 +++++++++++++ test/unit/actionsGeneratedTest.cpp | 57 +++++++++++++++++++++++++ test/unit/actionsTest.cpp | 30 ++++++------- 6 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 test/component/actionsGeneratedTest.cpp create mode 100644 test/unit/actionsGeneratedTest.cpp diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 6d047b5..613d840 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -17,16 +17,16 @@ class IActions { public: virtual ~IActions() = default; - virtual Result intent() const = 0; + virtual Result start(const std::string& intent) const = 0; virtual Result subscribeOnIntent(std::function&& notification) = 0; + virtual Result subscribeOnIntentChanged(std::function&& notification) { + return subscribeOnIntent(std::move(notification)); + } virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; - // Factory - static IActions* create(); - }; // class IActions } // namespace Firebolt::Actions diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index 33d09e4..7d04bc4 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -9,31 +9,26 @@ namespace Firebolt::Actions { ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) - : helper_(helper), - subscriptionManager_(helper, this) + : helper_(helper) + , subscriptionManager_(helper, this) {} -Result ActionsImpl::intent() const { - return helper_.get("actions.intent"); +Result ActionsImpl::start(const std::string& intent) const { + nlohmann::json params; + params["intent"] = intent; + return helper_.invoke("Actions.start", params); } -Result ActionsImpl::subscribeOnIntent(std::function&& notification) -{ +Result ActionsImpl::subscribeOnIntent(std::function&& notification) { return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); } -Result ActionsImpl::unsubscribe(SubscriptionId id) -{ +Result ActionsImpl::unsubscribe(SubscriptionId id) { return subscriptionManager_.unsubscribe(id); } -void ActionsImpl::unsubscribeAll() -{ +void ActionsImpl::unsubscribeAll() { subscriptionManager_.unsubscribeAll(); } -IActions* IActions::create() { - return new ActionsImpl(Firebolt::Helpers::GetHelperInstance()); -} - } // namespace Firebolt::Actions diff --git a/src/actions_impl.h b/src/actions_impl.h index c561970..e259cfb 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -12,11 +12,9 @@ namespace Firebolt::Actions { class ActionsImpl : public IActions { public: explicit ActionsImpl(Firebolt::Helpers::IHelper& helper); - ActionsImpl(const ActionsImpl&) = delete; - ActionsImpl& operator=(const ActionsImpl&) = delete; ~ActionsImpl() override = default; - Result intent() const override; + Result start(const std::string& intent) const override; Result subscribeOnIntent(std::function&& notification) override; diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp new file mode 100644 index 0000000..39f86ea --- /dev/null +++ b/test/component/actionsGeneratedTest.cpp @@ -0,0 +1,29 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "firebolt/actions.h" +#include + +TEST(ActionsGeneratedCTest, InterfaceSurfaceHasstart) +{ + using Interface = Firebolt::Actions::IActions; + auto ptr = &Interface::start; + (void)ptr; + SUCCEED(); +} + diff --git a/test/unit/actionsGeneratedTest.cpp b/test/unit/actionsGeneratedTest.cpp new file mode 100644 index 0000000..634c663 --- /dev/null +++ b/test/unit/actionsGeneratedTest.cpp @@ -0,0 +1,57 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mock_helper.h" +#include "actions_impl.h" +#include + +class ActionsGeneratedUTest : public ::testing::Test +{ +protected: + ::testing::NiceMock mockHelper; + Firebolt::Actions::ActionsImpl impl{mockHelper}; +}; + +TEST_F(ActionsGeneratedUTest, Constructs) +{ + SUCCEED(); +} + +TEST_F(ActionsGeneratedUTest, UnsubscribeForwardsToHelper) +{ + EXPECT_CALL(mockHelper, unsubscribe(7)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + + auto result = impl.unsubscribe(7); + ASSERT_TRUE(result) << "unsubscribe should return success when helper succeeds"; +} + + +TEST_F(ActionsGeneratedUTest, ForwardsstartTransportErrors) +{ + EXPECT_CALL(mockHelper, invoke("Actions.start", ::testing::_)) + .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& params) { + EXPECT_TRUE(params.is_object()) << "Expected params object for method call"; + EXPECT_TRUE(params.contains("intent")) << "Missing expected param key: intent"; + return Firebolt::Result{Firebolt::Error::General}; + })); + + auto result = impl.start({}); + EXPECT_FALSE(result) << "Expected error propagation when helper invoke fails"; +} + diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 1eafe27..b457e23 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -26,24 +26,16 @@ class ActionsUTest : public ::testing::Test, protected MockBase Firebolt::Actions::ActionsImpl actionsImpl_{mockHelper}; }; -TEST_F(ActionsUTest, Intent) +TEST_F(ActionsUTest, Start) { - const std::string expectedValue = "{\"intent\":\"launch\"}"; - mock_with_response("actions.intent", expectedValue); - - auto result = actionsImpl_.intent(); - - ASSERT_TRUE(result) << "ActionsImpl::intent() returned an error"; - EXPECT_EQ(*result, expectedValue); -} - -TEST_F(ActionsUTest, IntentBadResponse) -{ - mock_with_response("actions.intent", true); - - auto result = actionsImpl_.intent(); - - ASSERT_FALSE(result) << "ActionsImpl::intent() did not return an error"; + nlohmann::json expectedParams; + expectedParams["intent"] = "launch"; + EXPECT_CALL(mockHelper, invoke("Actions.start", expectedParams)) + .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) + { return Firebolt::Result{Firebolt::Error::None}; })); + + auto result = actionsImpl_.start("launch"); + EXPECT_TRUE(result); } TEST_F(ActionsUTest, SubscribeOnIntent) @@ -56,5 +48,7 @@ TEST_F(ActionsUTest, SubscribeOnIntent) ASSERT_TRUE(result) << "ActionsImpl::subscribeOnIntent() returned an error"; EXPECT_EQ(*result, expectedValue); - actionsImpl_.unsubscribe(*result); + auto unsubResult = actionsImpl_.unsubscribe(*result); + ASSERT_TRUE(unsubResult) << "ActionsImpl::unsubscribe() returned an error"; } + From 87dc59c186720bbe53c1b06c6920ef4009fa6445 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 13:35:10 -0700 Subject: [PATCH 03/15] chore(actions): regenerate json_types with JsonData namespace --- src/json_types/actions.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/json_types/actions.h b/src/json_types/actions.h index e8d71b1..066e915 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -12,6 +12,10 @@ namespace Firebolt::Actions { +namespace JsonData { + +} // namespace JsonData + } // namespace Firebolt::Actions #endif // FIREBOLT_ACTIONS_JSON_H From a201befba2664ee8d34b7061f5c326aee82d83ee Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 13:47:44 -0700 Subject: [PATCH 04/15] fix(scripts): validate option args and guard install without sysroot --- build.sh | 5 +++++ lint.sh | 10 ++++++++++ run-all-test.sh | 10 ++++++++++ run-component-tests.sh | 5 +++++ run-unit-tests.sh | 5 +++++ setup-device-tunnel.sh | 15 +++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/build.sh b/build.sh index 3482ffd..c155223 100755 --- a/build.sh +++ b/build.sh @@ -58,6 +58,11 @@ else echo "SYSROOT_PATH not set; building without SYSROOT_PATH override" fi +if [[ "$do_install" == true && "$bdir" == "build" && -z "${SYSROOT_PATH:-}" ]]; then + echo "Refusing --install without --sysroot to avoid host install into /usr" >&2 + exit 1 +fi + $cleanFirst && rm -rf $bdir if [[ ! -e "$bdir" || -n "$@" ]]; then diff --git a/lint.sh b/lint.sh index f0b4768..999560a 100755 --- a/lint.sh +++ b/lint.sh @@ -63,10 +63,20 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --build-dir) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --build-dir" >&2 + usage + exit 1 + fi BUILD_DIR="${2:-}" shift ;; --tidy-path) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --tidy-path" >&2 + usage + exit 1 + fi CLANG_TIDY_PATHS+=("${2:-}") shift ;; diff --git a/run-all-test.sh b/run-all-test.sh index 028d7f9..d8e9cdc 100755 --- a/run-all-test.sh +++ b/run-all-test.sh @@ -63,10 +63,20 @@ while [[ $# -gt 0 ]]; do RUN_UNIT=false ;; --unit-filter) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --unit-filter" >&2 + usage + exit 1 + fi UNIT_FILTER="${2:-}" shift ;; --component-filter) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --component-filter" >&2 + usage + exit 1 + fi COMPONENT_FILTER="${2:-}" shift ;; diff --git a/run-component-tests.sh b/run-component-tests.sh index e3162be..e1615bb 100755 --- a/run-component-tests.sh +++ b/run-component-tests.sh @@ -56,6 +56,11 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --component-filter) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --component-filter" >&2 + usage + exit 1 + fi COMPONENT_FILTER="${2:-}" shift ;; diff --git a/run-unit-tests.sh b/run-unit-tests.sh index c2f2706..c7ba317 100755 --- a/run-unit-tests.sh +++ b/run-unit-tests.sh @@ -56,6 +56,11 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --unit-filter) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --unit-filter" >&2 + usage + exit 1 + fi UNIT_FILTER="${2:-}" shift ;; diff --git a/setup-device-tunnel.sh b/setup-device-tunnel.sh index 49927a7..73a3e43 100755 --- a/setup-device-tunnel.sh +++ b/setup-device-tunnel.sh @@ -70,14 +70,29 @@ EOF while [[ $# -gt 0 ]]; do case "$1" in --local-port) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --local-port" >&2 + usage + exit 1 + fi LOCAL_PORT="${2:-}" shift ;; --remote-port) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --remote-port" >&2 + usage + exit 1 + fi REMOTE_PORT="${2:-}" shift ;; --remote-bind) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Missing value for --remote-bind" >&2 + usage + exit 1 + fi REMOTE_BIND_ADDR="${2:-}" shift ;; From 2a7e03cbffae841050bfce9343e4885c5c116b57 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 14:15:54 -0700 Subject: [PATCH 05/15] feat(actions): rename start API to intent --- include/firebolt/actions.h | 2 +- src/actions_impl.cpp | 4 ++-- src/actions_impl.h | 2 +- test/component/actionsGeneratedTest.cpp | 4 ++-- test/unit/actionsGeneratedTest.cpp | 6 +++--- test/unit/actionsTest.cpp | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 613d840..4c5a8ec 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -17,7 +17,7 @@ class IActions { public: virtual ~IActions() = default; - virtual Result start(const std::string& intent) const = 0; + virtual Result intent(const std::string& intent) const = 0; virtual Result subscribeOnIntent(std::function&& notification) = 0; virtual Result subscribeOnIntentChanged(std::function&& notification) { diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index 7d04bc4..1c95da8 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -13,10 +13,10 @@ ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) , subscriptionManager_(helper, this) {} -Result ActionsImpl::start(const std::string& intent) const { +Result ActionsImpl::intent(const std::string& intent) const { nlohmann::json params; params["intent"] = intent; - return helper_.invoke("Actions.start", params); + return helper_.invoke("Actions.intent", params); } Result ActionsImpl::subscribeOnIntent(std::function&& notification) { diff --git a/src/actions_impl.h b/src/actions_impl.h index e259cfb..989c3a9 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -14,7 +14,7 @@ class ActionsImpl : public IActions { explicit ActionsImpl(Firebolt::Helpers::IHelper& helper); ~ActionsImpl() override = default; - Result start(const std::string& intent) const override; + Result intent(const std::string& intent) const override; Result subscribeOnIntent(std::function&& notification) override; diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index 39f86ea..a08372b 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -19,10 +19,10 @@ #include "firebolt/actions.h" #include -TEST(ActionsGeneratedCTest, InterfaceSurfaceHasstart) +TEST(ActionsGeneratedCTest, InterfaceSurfaceHasintent) { using Interface = Firebolt::Actions::IActions; - auto ptr = &Interface::start; + auto ptr = &Interface::intent; (void)ptr; SUCCEED(); } diff --git a/test/unit/actionsGeneratedTest.cpp b/test/unit/actionsGeneratedTest.cpp index 634c663..4c0b71e 100644 --- a/test/unit/actionsGeneratedTest.cpp +++ b/test/unit/actionsGeneratedTest.cpp @@ -42,16 +42,16 @@ TEST_F(ActionsGeneratedUTest, UnsubscribeForwardsToHelper) } -TEST_F(ActionsGeneratedUTest, ForwardsstartTransportErrors) +TEST_F(ActionsGeneratedUTest, ForwardsintentTransportErrors) { - EXPECT_CALL(mockHelper, invoke("Actions.start", ::testing::_)) + EXPECT_CALL(mockHelper, invoke("Actions.intent", ::testing::_)) .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& params) { EXPECT_TRUE(params.is_object()) << "Expected params object for method call"; EXPECT_TRUE(params.contains("intent")) << "Missing expected param key: intent"; return Firebolt::Result{Firebolt::Error::General}; })); - auto result = impl.start({}); + auto result = impl.intent({}); EXPECT_FALSE(result) << "Expected error propagation when helper invoke fails"; } diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index b457e23..7444bd1 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -30,11 +30,11 @@ TEST_F(ActionsUTest, Start) { nlohmann::json expectedParams; expectedParams["intent"] = "launch"; - EXPECT_CALL(mockHelper, invoke("Actions.start", expectedParams)) + EXPECT_CALL(mockHelper, invoke("Actions.intent", expectedParams)) .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) { return Firebolt::Result{Firebolt::Error::None}; })); - auto result = actionsImpl_.start("launch"); + auto result = actionsImpl_.intent("launch"); EXPECT_TRUE(result); } From e6c37d3c38266af1ac95b13753de04c4041235ac Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 14:38:40 -0700 Subject: [PATCH 06/15] fix(actions): address review feedback on headers and scripts --- include/firebolt/actions.h | 18 ++++++++++++++++++ lint.sh | 9 +++++++-- run-all-test.sh | 4 ++-- run-component-tests.sh | 2 +- run-unit-tests.sh | 2 +- setup-device-tunnel.sh | 8 +++----- src/actions_impl.cpp | 18 ++++++++++++++++++ src/actions_impl.h | 18 ++++++++++++++++++ src/json_types/actions.h | 18 ++++++++++++++++++ 9 files changed, 86 insertions(+), 11 deletions(-) diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 4c5a8ec..6ee6625 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -1,3 +1,21 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// // ============================================================================ // AUTO-GENERATED by fb-gen — DO NOT EDIT // ============================================================================ diff --git a/lint.sh b/lint.sh index 999560a..e574b3e 100755 --- a/lint.sh +++ b/lint.sh @@ -63,7 +63,7 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --build-dir) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --build-dir" >&2 usage exit 1 @@ -72,7 +72,7 @@ while [[ $# -gt 0 ]]; do shift ;; --tidy-path) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --tidy-path" >&2 usage exit 1 @@ -106,6 +106,11 @@ done cd "$ROOT_DIR" +if [[ "$NO_BUILD" == false && "$BUILD_DIR" != "build-dev" ]]; then + echo "--build-dir is only supported with --no-build (build step always uses build-dev)." >&2 + exit 1 +fi + if [[ "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then echo "Nothing to run: both clang-tidy and cppcheck are disabled." >&2 exit 1 diff --git a/run-all-test.sh b/run-all-test.sh index d8e9cdc..bc0b324 100755 --- a/run-all-test.sh +++ b/run-all-test.sh @@ -63,7 +63,7 @@ while [[ $# -gt 0 ]]; do RUN_UNIT=false ;; --unit-filter) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --unit-filter" >&2 usage exit 1 @@ -72,7 +72,7 @@ while [[ $# -gt 0 ]]; do shift ;; --component-filter) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --component-filter" >&2 usage exit 1 diff --git a/run-component-tests.sh b/run-component-tests.sh index e1615bb..859d91a 100755 --- a/run-component-tests.sh +++ b/run-component-tests.sh @@ -56,7 +56,7 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --component-filter) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --component-filter" >&2 usage exit 1 diff --git a/run-unit-tests.sh b/run-unit-tests.sh index c7ba317..0c36a6e 100755 --- a/run-unit-tests.sh +++ b/run-unit-tests.sh @@ -56,7 +56,7 @@ while [[ $# -gt 0 ]]; do NO_BUILD=true ;; --unit-filter) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then echo "Missing value for --unit-filter" >&2 usage exit 1 diff --git a/setup-device-tunnel.sh b/setup-device-tunnel.sh index 73a3e43..e3f7843 100755 --- a/setup-device-tunnel.sh +++ b/setup-device-tunnel.sh @@ -123,11 +123,9 @@ SSH_ARGS=(-N -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ExitOnForwa echo "Opening SSH tunnel: localhost:${LOCAL_PORT} -> ${REMOTE_BIND_ADDR}:${REMOTE_PORT} on ${TARGET}" if [[ "$BACKGROUND" == true ]]; then - LOG_FILE="${TMPDIR:-/tmp}/firebolt-tunnel-${LOCAL_PORT}.log" - nohup ssh "${SSH_ARGS[@]}" >"$LOG_FILE" 2>&1 & - SSH_PID=$! - echo "Tunnel PID: ${SSH_PID}" - echo "Tunnel log: ${LOG_FILE}" + # -f backgrounds only after authentication and forwarding are set up. + ssh -f "${SSH_ARGS[@]}" + echo "Tunnel established in background mode." exit 0 fi diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index 1c95da8..edb3d35 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -1,3 +1,21 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// // ============================================================================ // AUTO-GENERATED by fb-gen — DO NOT EDIT // ============================================================================ diff --git a/src/actions_impl.h b/src/actions_impl.h index 989c3a9..eb964e0 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -1,3 +1,21 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// // ============================================================================ // AUTO-GENERATED by fb-gen — DO NOT EDIT // ============================================================================ diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 066e915..6025350 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -1,3 +1,21 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// // ============================================================================ // AUTO-GENERATED by fb-gen — DO NOT EDIT // ============================================================================ From da592f38da9dd453a9a98965374dce81ab785181 Mon Sep 17 00:00:00 2001 From: Brendan O'Bra Date: Wed, 27 May 2026 14:48:24 -0700 Subject: [PATCH 07/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.sh b/build.sh index c155223..6dac83d 100755 --- a/build.sh +++ b/build.sh @@ -55,6 +55,10 @@ if [[ -n "$SYSROOT_PATH" ]]; then [[ -e "$SYSROOT_PATH" ]] || { echo "SYSROOT_PATH not exist ($SYSROOT_PATH)" >/dev/stderr; exit 1; } params+=" -DSYSROOT_PATH=$SYSROOT_PATH" else + if $do_install; then + echo "--install requires --sysroot to be set; refusing to install without SYSROOT_PATH" >/dev/stderr + exit 1 + fi echo "SYSROOT_PATH not set; building without SYSROOT_PATH override" fi From d744c05a1bc559fa5d108e75f8f24055fdd93bdf Mon Sep 17 00:00:00 2001 From: Brendan O'Bra Date: Wed, 27 May 2026 14:51:34 -0700 Subject: [PATCH 08/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/firebolt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firebolt.cpp b/src/firebolt.cpp index 48ec277..bb44f45 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -41,7 +41,7 @@ class FireboltAccessorImpl : public IFireboltAccessor FireboltAccessorImpl() : accessibility_(Firebolt::Helpers::GetHelperInstance()), advertising_(Firebolt::Helpers::GetHelperInstance()), - actions_(Firebolt::Helpers::GetHelperInstance()), + actions_(Firebolt::Helpers::GetHelperInstance()), device_(Firebolt::Helpers::GetHelperInstance()), discovery_(Firebolt::Helpers::GetHelperInstance()), display_(Firebolt::Helpers::GetHelperInstance()), From 118d76a69aacdf0c2a118f3cfa40f2197365d78f Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 14:55:01 -0700 Subject: [PATCH 09/15] fix(review): harden thread resolver and make ActionsImpl non-copyable --- resolve-review-threads.sh | 251 ++++++++++++++++++++++++++++++++++++++ src/actions_impl.h | 2 + 2 files changed, 253 insertions(+) create mode 100755 resolve-review-threads.sh diff --git a/resolve-review-threads.sh b/resolve-review-threads.sh new file mode 100755 index 0000000..c31d106 --- /dev/null +++ b/resolve-review-threads.sh @@ -0,0 +1,251 @@ +#!/usr/bin/env bash + +set -euo pipefail + +die() { + echo "Error: $*" >&2 + exit 1 +} + +require_cmd() { + local cmd="$1" + local install_hint="$2" + if ! command -v "$cmd" >/dev/null 2>&1; then + die "Missing required dependency '$cmd'. ${install_hint}" + fi +} + +preflight_checks() { + if [[ -z "${BASH_VERSION:-}" ]]; then + die "This script must be run with bash. Example: bash ./resolve-review-threads.sh " + fi + + if (( BASH_VERSINFO[0] < 4 )); then + die "Bash 4+ is required (mapfile is used). Current bash version: ${BASH_VERSION}" + fi + + require_cmd "gh" "Install GitHub CLI: https://cli.github.com/" + require_cmd "jq" "Install jq (for example: sudo apt-get install jq)" + + if ! gh auth status >/dev/null 2>&1; then + die "GitHub CLI is not authenticated. Run: gh auth login" + fi + + if ! gh api graphql -f query='query { viewer { login } }' >/dev/null 2>&1; then + die "GitHub GraphQL API call failed. Check network connectivity and token permissions for gh auth." + fi +} + +usage() { + cat <<'EOF' +Usage: ./resolve-review-threads.sh [options] + +Resolves unresolved PR review threads that you are allowed to resolve. + +Options: + --repo Repository to target. Default: current gh repo + --all-unresolved Resolve all unresolved resolvable threads + --dry-run Print what would be resolved without mutating anything + --help Show this help + +Examples: + ./resolve-review-threads.sh 63 + ./resolve-review-threads.sh 63 --all-unresolved + ./resolve-review-threads.sh 63 --repo rdkcentral/firebolt-cpp-client +EOF +} + +preflight_checks + +if [[ $# -lt 1 ]]; then + usage + exit 1 +fi + +PR_NUMBER="" +REPO="" +OUTDATED_ONLY=false +RESOLVE_ALL=false +DRY_RUN=false + +if [[ "$1" =~ ^[0-9]+$ ]]; then + PR_NUMBER="$1" + shift +else + echo "Error: first argument must be a PR number." >&2 + usage + exit 1 +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then + echo "Error: --repo requires owner/repo." >&2 + exit 1 + fi + REPO="$2" + shift 2 + ;; + --all-unresolved) + RESOLVE_ALL=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Error: unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +# Default mode is outdated-only unless explicitly overridden. +if [[ "$RESOLVE_ALL" == "true" ]]; then + OUTDATED_ONLY=false +else + OUTDATED_ONLY=true +fi + +if [[ -z "$REPO" ]]; then + REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)" +fi + +if [[ "$REPO" != */* ]]; then + die "Invalid repository value '$REPO'. Expected format: owner/repo" +fi + +OWNER="${REPO%%/*}" +NAME="${REPO##*/}" + +QUERY_FIRST='query($owner:String!, $name:String!, $number:Int!) { + repository(owner:$owner, name:$name) { + pullRequest(number:$number) { + reviewThreads(first:100) { + pageInfo { hasNextPage endCursor } + nodes { + id + isResolved + isOutdated + viewerCanResolve + } + } + } + } +}' + +QUERY_NEXT='query($owner:String!, $name:String!, $number:Int!, $cursor:String!) { + repository(owner:$owner, name:$name) { + pullRequest(number:$number) { + reviewThreads(first:100, after:$cursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + isResolved + isOutdated + viewerCanResolve + } + } + } + } +}' + +MUTATION_RESOLVE='mutation($id:ID!) { + resolveReviewThread(input:{threadId:$id}) { + thread { id isResolved } + } +}' + +cursor="" +has_next=true + +declare -a THREAD_IDS=() +total_threads=0 +unresolved_threads=0 +unresolved_outdated_threads=0 +unresolved_resolvable_threads=0 +unresolved_unresolvable_threads=0 + +while [[ "$has_next" == "true" ]]; do + if [[ -z "$cursor" ]]; then + resp="$(gh api graphql -f query="$QUERY_FIRST" -F owner="$OWNER" -F name="$NAME" -F number="$PR_NUMBER")" + else + resp="$(gh api graphql -f query="$QUERY_NEXT" -F owner="$OWNER" -F name="$NAME" -F number="$PR_NUMBER" -F cursor="$cursor")" + fi + + if [[ "$(echo "$resp" | jq -r '.data.repository.pullRequest == null')" == "true" ]]; then + echo "Error: PR #$PR_NUMBER not found in $REPO" >&2 + exit 1 + fi + + page_total="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes | length')" + page_unresolved="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not)] | length')" + page_unresolved_outdated="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.isOutdated == true))] | length')" + page_unresolved_resolvable="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true))] | length')" + page_unresolved_unresolvable="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == false))] | length')" + + total_threads=$((total_threads + page_total)) + unresolved_threads=$((unresolved_threads + page_unresolved)) + unresolved_outdated_threads=$((unresolved_outdated_threads + page_unresolved_outdated)) + unresolved_resolvable_threads=$((unresolved_resolvable_threads + page_unresolved_resolvable)) + unresolved_unresolvable_threads=$((unresolved_unresolvable_threads + page_unresolved_unresolvable)) + + if [[ "$OUTDATED_ONLY" == "true" ]]; then + mapfile -t page_ids < <(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true) and (.isOutdated == true)) | .id') + else + mapfile -t page_ids < <(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true)) | .id') + fi + + if [[ ${#page_ids[@]} -gt 0 ]]; then + THREAD_IDS+=("${page_ids[@]}") + fi + + has_next="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')" + cursor="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor // ""')" +done + +if [[ ${#THREAD_IDS[@]} -eq 0 ]]; then + echo "Target repo: $REPO" + echo "PR: #$PR_NUMBER" + echo "Threads total: $total_threads" + echo "Unresolved: $unresolved_threads" + echo "Unresolved outdated: $unresolved_outdated_threads" + echo "Unresolved resolvable by current user: $unresolved_resolvable_threads" + echo "Unresolved NOT resolvable by current user: $unresolved_unresolvable_threads" + + if [[ "$unresolved_threads" -gt 0 && "$unresolved_resolvable_threads" -eq 0 ]]; then + echo "Note: unresolved threads exist, but your account cannot resolve them (viewerCanResolve=false)." + fi + + if [[ "$OUTDATED_ONLY" == "true" ]]; then + echo "No unresolved outdated threads to resolve in $REPO PR #$PR_NUMBER." + else + echo "No unresolved resolvable threads to resolve in $REPO PR #$PR_NUMBER." + fi + exit 0 +fi + +echo "Target repo: $REPO" +echo "PR: #$PR_NUMBER" +echo "Threads to resolve: ${#THREAD_IDS[@]}" + +if [[ "$DRY_RUN" == "true" ]]; then + echo "Dry run enabled. Thread IDs:" + printf '%s\n' "${THREAD_IDS[@]}" + exit 0 +fi + +resolved=0 +for thread_id in "${THREAD_IDS[@]}"; do + gh api graphql -f query="$MUTATION_RESOLVE" -F id="$thread_id" >/dev/null + resolved=$((resolved + 1)) +done + +echo "Resolved $resolved thread(s)." diff --git a/src/actions_impl.h b/src/actions_impl.h index eb964e0..157c88f 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -30,6 +30,8 @@ namespace Firebolt::Actions { class ActionsImpl : public IActions { public: explicit ActionsImpl(Firebolt::Helpers::IHelper& helper); + ActionsImpl(const ActionsImpl&) = delete; + ActionsImpl& operator=(const ActionsImpl&) = delete; ~ActionsImpl() override = default; Result intent(const std::string& intent) const override; From 8a5f934d39b7b07f834812f7f263727bbf778d33 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 14:56:32 -0700 Subject: [PATCH 10/15] fix(review): address remaining ABI and include comments --- include/firebolt/actions.h | 1 + include/firebolt/firebolt.h | 14 +++++++------- src/firebolt.cpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 6ee6625..762171b 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index 003414e..4003c32 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -93,13 +93,6 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor */ virtual Advertising::IAdvertising& AdvertisingInterface() = 0; - /** - * @brief Returns instance of Actions interface - * - * @return Reference to Actions interface - */ - virtual Actions::IActions& ActionsInterface() = 0; - /** * @brief Returns instance of Device interface * @@ -169,5 +162,12 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor * @return Reference to TextToSpeech interface */ virtual TextToSpeech::ITextToSpeech& TextToSpeechInterface() = 0; + + /** + * @brief Returns instance of Actions interface + * + * @return Reference to Actions interface + */ + virtual Actions::IActions& ActionsInterface() = 0; }; } // namespace Firebolt diff --git a/src/firebolt.cpp b/src/firebolt.cpp index bb44f45..88f945f 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -75,7 +75,6 @@ class FireboltAccessorImpl : public IFireboltAccessor Accessibility::IAccessibility& AccessibilityInterface() override { return accessibility_; } Advertising::IAdvertising& AdvertisingInterface() override { return advertising_; } - Actions::IActions& ActionsInterface() override { return actions_; } Device::IDevice& DeviceInterface() override { return device_; } Discovery::IDiscovery& DiscoveryInterface() override { return discovery_; } Display::IDisplay& DisplayInterface() override { return display_; } @@ -86,6 +85,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Presentation::IPresentation& PresentationInterface() override { return presentation_; } Stats::IStats& StatsInterface() override { return stats_; } TextToSpeech::ITextToSpeech& TextToSpeechInterface() override { return textToSpeech_; } + Actions::IActions& ActionsInterface() override { return actions_; } private: void unsubscribeAll() From 9d86e6c17bee1678fb21ea785c814d92a5fb4fe0 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 14:57:18 -0700 Subject: [PATCH 11/15] test(actions): add component coverage for intent and onIntent --- test/component/actionsGeneratedTest.cpp | 43 +++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index a08372b..aff810c 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -16,14 +16,45 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "firebolt/actions.h" +#include "firebolt/firebolt.h" +#include "utils.h" #include -TEST(ActionsGeneratedCTest, InterfaceSurfaceHasintent) +class ActionsGeneratedCTest : public ::testing::Test { - using Interface = Firebolt::Actions::IActions; - auto ptr = &Interface::intent; - (void)ptr; - SUCCEED(); +protected: + void SetUp() override { eventReceived = false; } + + std::condition_variable cv; + std::mutex mtx; + bool eventReceived; +}; + +TEST_F(ActionsGeneratedCTest, Intent) +{ + auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent("launch"); + EXPECT_TRUE(result) << toError(result); +} + +TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) +{ + auto id = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent( + [&](const std::string& intent) + { + EXPECT_EQ(intent, "launch"); + { + std::lock_guard lock(mtx); + eventReceived = true; + } + cv.notify_one(); + }); + + verifyEventSubscription(id); + + triggerEvent("Actions.onIntent", R"({"value":"launch"})"); + verifyEventReceived(mtx, cv, eventReceived); + + auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id.value()); + verifyUnsubscribeResult(result); } From e3506a8d47e2a83d57e8a6d0e966686be947a962 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 15:02:15 -0700 Subject: [PATCH 12/15] fix(review): address latest CI and script feedback --- resolve-review-threads.sh | 24 ++++++++++++++++++++++++ test/ComponentTestsMain.cpp | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/resolve-review-threads.sh b/resolve-review-threads.sh index c31d106..6cd4c34 100755 --- a/resolve-review-threads.sh +++ b/resolve-review-threads.sh @@ -1,5 +1,21 @@ #!/usr/bin/env bash +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + set -euo pipefail die() { @@ -55,6 +71,14 @@ Examples: EOF } +# Allow usage output in unconfigured environments. +for arg in "$@"; do + if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then + usage + exit 0 + fi +done + preflight_checks if [[ $# -lt 1 ]]; then diff --git a/test/ComponentTestsMain.cpp b/test/ComponentTestsMain.cpp index dd58c01..578ae28 100644 --- a/test/ComponentTestsMain.cpp +++ b/test/ComponentTestsMain.cpp @@ -70,7 +70,7 @@ bool waitOnConnectionReady() int main(int argc, char** argv) { const char* endpoint = std::getenv("FIREBOLT_ENDPOINT"); - string url = (endpoint && endpoint[0] != '\0') ? endpoint : "ws://127.0.0.1:3474/"; + string url = (endpoint && endpoint[0] != '\0') ? endpoint : "ws://127.0.0.1:9998/"; cout << "Using Firebolt endpoint: " << url << endl; createFireboltInstance(url); From d416d8afd86736198209d96b72aa9e8bf3a5e57f Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 15:26:19 -0700 Subject: [PATCH 13/15] chore: remove review-thread helper script from PR --- resolve-review-threads.sh | 275 -------------------------------------- 1 file changed, 275 deletions(-) delete mode 100755 resolve-review-threads.sh diff --git a/resolve-review-threads.sh b/resolve-review-threads.sh deleted file mode 100755 index 6cd4c34..0000000 --- a/resolve-review-threads.sh +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2026 Comcast Cable Communications Management, LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -die() { - echo "Error: $*" >&2 - exit 1 -} - -require_cmd() { - local cmd="$1" - local install_hint="$2" - if ! command -v "$cmd" >/dev/null 2>&1; then - die "Missing required dependency '$cmd'. ${install_hint}" - fi -} - -preflight_checks() { - if [[ -z "${BASH_VERSION:-}" ]]; then - die "This script must be run with bash. Example: bash ./resolve-review-threads.sh " - fi - - if (( BASH_VERSINFO[0] < 4 )); then - die "Bash 4+ is required (mapfile is used). Current bash version: ${BASH_VERSION}" - fi - - require_cmd "gh" "Install GitHub CLI: https://cli.github.com/" - require_cmd "jq" "Install jq (for example: sudo apt-get install jq)" - - if ! gh auth status >/dev/null 2>&1; then - die "GitHub CLI is not authenticated. Run: gh auth login" - fi - - if ! gh api graphql -f query='query { viewer { login } }' >/dev/null 2>&1; then - die "GitHub GraphQL API call failed. Check network connectivity and token permissions for gh auth." - fi -} - -usage() { - cat <<'EOF' -Usage: ./resolve-review-threads.sh [options] - -Resolves unresolved PR review threads that you are allowed to resolve. - -Options: - --repo Repository to target. Default: current gh repo - --all-unresolved Resolve all unresolved resolvable threads - --dry-run Print what would be resolved without mutating anything - --help Show this help - -Examples: - ./resolve-review-threads.sh 63 - ./resolve-review-threads.sh 63 --all-unresolved - ./resolve-review-threads.sh 63 --repo rdkcentral/firebolt-cpp-client -EOF -} - -# Allow usage output in unconfigured environments. -for arg in "$@"; do - if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then - usage - exit 0 - fi -done - -preflight_checks - -if [[ $# -lt 1 ]]; then - usage - exit 1 -fi - -PR_NUMBER="" -REPO="" -OUTDATED_ONLY=false -RESOLVE_ALL=false -DRY_RUN=false - -if [[ "$1" =~ ^[0-9]+$ ]]; then - PR_NUMBER="$1" - shift -else - echo "Error: first argument must be a PR number." >&2 - usage - exit 1 -fi - -while [[ $# -gt 0 ]]; do - case "$1" in - --repo) - if [[ $# -lt 2 || -z "${2:-}" || "${2:0:1}" == "-" ]]; then - echo "Error: --repo requires owner/repo." >&2 - exit 1 - fi - REPO="$2" - shift 2 - ;; - --all-unresolved) - RESOLVE_ALL=true - shift - ;; - --dry-run) - DRY_RUN=true - shift - ;; - --help|-h) - usage - exit 0 - ;; - *) - echo "Error: unknown option: $1" >&2 - usage - exit 1 - ;; - esac -done - -# Default mode is outdated-only unless explicitly overridden. -if [[ "$RESOLVE_ALL" == "true" ]]; then - OUTDATED_ONLY=false -else - OUTDATED_ONLY=true -fi - -if [[ -z "$REPO" ]]; then - REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)" -fi - -if [[ "$REPO" != */* ]]; then - die "Invalid repository value '$REPO'. Expected format: owner/repo" -fi - -OWNER="${REPO%%/*}" -NAME="${REPO##*/}" - -QUERY_FIRST='query($owner:String!, $name:String!, $number:Int!) { - repository(owner:$owner, name:$name) { - pullRequest(number:$number) { - reviewThreads(first:100) { - pageInfo { hasNextPage endCursor } - nodes { - id - isResolved - isOutdated - viewerCanResolve - } - } - } - } -}' - -QUERY_NEXT='query($owner:String!, $name:String!, $number:Int!, $cursor:String!) { - repository(owner:$owner, name:$name) { - pullRequest(number:$number) { - reviewThreads(first:100, after:$cursor) { - pageInfo { hasNextPage endCursor } - nodes { - id - isResolved - isOutdated - viewerCanResolve - } - } - } - } -}' - -MUTATION_RESOLVE='mutation($id:ID!) { - resolveReviewThread(input:{threadId:$id}) { - thread { id isResolved } - } -}' - -cursor="" -has_next=true - -declare -a THREAD_IDS=() -total_threads=0 -unresolved_threads=0 -unresolved_outdated_threads=0 -unresolved_resolvable_threads=0 -unresolved_unresolvable_threads=0 - -while [[ "$has_next" == "true" ]]; do - if [[ -z "$cursor" ]]; then - resp="$(gh api graphql -f query="$QUERY_FIRST" -F owner="$OWNER" -F name="$NAME" -F number="$PR_NUMBER")" - else - resp="$(gh api graphql -f query="$QUERY_NEXT" -F owner="$OWNER" -F name="$NAME" -F number="$PR_NUMBER" -F cursor="$cursor")" - fi - - if [[ "$(echo "$resp" | jq -r '.data.repository.pullRequest == null')" == "true" ]]; then - echo "Error: PR #$PR_NUMBER not found in $REPO" >&2 - exit 1 - fi - - page_total="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes | length')" - page_unresolved="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not)] | length')" - page_unresolved_outdated="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.isOutdated == true))] | length')" - page_unresolved_resolvable="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true))] | length')" - page_unresolved_unresolvable="$(echo "$resp" | jq -r '[.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == false))] | length')" - - total_threads=$((total_threads + page_total)) - unresolved_threads=$((unresolved_threads + page_unresolved)) - unresolved_outdated_threads=$((unresolved_outdated_threads + page_unresolved_outdated)) - unresolved_resolvable_threads=$((unresolved_resolvable_threads + page_unresolved_resolvable)) - unresolved_unresolvable_threads=$((unresolved_unresolvable_threads + page_unresolved_unresolvable)) - - if [[ "$OUTDATED_ONLY" == "true" ]]; then - mapfile -t page_ids < <(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true) and (.isOutdated == true)) | .id') - else - mapfile -t page_ids < <(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select((.isResolved | not) and (.viewerCanResolve == true)) | .id') - fi - - if [[ ${#page_ids[@]} -gt 0 ]]; then - THREAD_IDS+=("${page_ids[@]}") - fi - - has_next="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')" - cursor="$(echo "$resp" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor // ""')" -done - -if [[ ${#THREAD_IDS[@]} -eq 0 ]]; then - echo "Target repo: $REPO" - echo "PR: #$PR_NUMBER" - echo "Threads total: $total_threads" - echo "Unresolved: $unresolved_threads" - echo "Unresolved outdated: $unresolved_outdated_threads" - echo "Unresolved resolvable by current user: $unresolved_resolvable_threads" - echo "Unresolved NOT resolvable by current user: $unresolved_unresolvable_threads" - - if [[ "$unresolved_threads" -gt 0 && "$unresolved_resolvable_threads" -eq 0 ]]; then - echo "Note: unresolved threads exist, but your account cannot resolve them (viewerCanResolve=false)." - fi - - if [[ "$OUTDATED_ONLY" == "true" ]]; then - echo "No unresolved outdated threads to resolve in $REPO PR #$PR_NUMBER." - else - echo "No unresolved resolvable threads to resolve in $REPO PR #$PR_NUMBER." - fi - exit 0 -fi - -echo "Target repo: $REPO" -echo "PR: #$PR_NUMBER" -echo "Threads to resolve: ${#THREAD_IDS[@]}" - -if [[ "$DRY_RUN" == "true" ]]; then - echo "Dry run enabled. Thread IDs:" - printf '%s\n' "${THREAD_IDS[@]}" - exit 0 -fi - -resolved=0 -for thread_id in "${THREAD_IDS[@]}"; do - gh api graphql -f query="$MUTATION_RESOLVE" -F id="$thread_id" >/dev/null - resolved=$((resolved + 1)) -done - -echo "Resolved $resolved thread(s)." From 542310003cd0177c35230debdd4fa71f36ef07fb Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 15:41:39 -0700 Subject: [PATCH 14/15] fix(lint): add clang-format check/fix and apply formatting --- include/firebolt/actions.h | 15 ++-- include/firebolt/firebolt.h | 2 +- lint.sh | 105 ++++++++++++++++++++++-- src/actions_impl.cpp | 22 +++-- src/actions_impl.h | 6 +- src/firebolt.cpp | 2 +- src/json_types/actions.h | 12 +-- test/component/actionsGeneratedTest.cpp | 1 - test/unit/actionsGeneratedTest.cpp | 19 ++--- test/unit/actionsTest.cpp | 3 +- test/utils.cpp | 4 +- test/utils.h | 4 +- 12 files changed, 148 insertions(+), 47 deletions(-) diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 762171b..9c39198 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -22,31 +22,34 @@ #ifndef FIREBOLT_ACTIONS_H #define FIREBOLT_ACTIONS_H +#include #include #include #include -#include #include +#include #include -#include -namespace Firebolt::Actions { +namespace Firebolt::Actions +{ -class IActions { +class IActions +{ public: virtual ~IActions() = default; virtual Result intent(const std::string& intent) const = 0; virtual Result subscribeOnIntent(std::function&& notification) = 0; - virtual Result subscribeOnIntentChanged(std::function&& notification) { + virtual Result subscribeOnIntentChanged(std::function&& notification) + { return subscribeOnIntent(std::move(notification)); } virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; -}; // class IActions +}; // class IActions } // namespace Firebolt::Actions diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index 4003c32..d889ec0 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -19,8 +19,8 @@ #pragma once #include "firebolt/accessibility.h" -#include "firebolt/advertising.h" #include "firebolt/actions.h" +#include "firebolt/advertising.h" #include "firebolt/client_export.h" #include "firebolt/device.h" #include "firebolt/discovery.h" diff --git a/lint.sh b/lint.sh index e574b3e..9487bd0 100755 --- a/lint.sh +++ b/lint.sh @@ -24,23 +24,30 @@ NO_BUILD=false CLEAN=false RUN_CLANG_TIDY=true RUN_CPPCHECK=true +RUN_CLANG_FORMAT=true APPLY_FIXES=false +FORMAT_FIX=false CLANG_TIDY_PATHS=(src include test/unit test/component) +CLANG_FORMAT_PATHS=(src include test) usage() { cat < Build directory containing compile_commands.json (default: build-dev) --tidy-path

Add path for clang-tidy scan (repeatable) + --format-path

Add path for clang-format scan (repeatable) --fix Apply clang-tidy fix-its (clang-tidy only) + --format-fix Apply clang-format fixes in-place + --format-only Run clang-format only + --no-format Skip clang-format checks --tidy-only Run clang-tidy only --cppcheck-only Run cppcheck only --help Show this help @@ -49,7 +56,10 @@ Examples: ./lint.sh ./lint.sh --tidy-only ./lint.sh --tidy-only --fix + ./lint.sh --format-only + ./lint.sh --format-fix ./lint.sh --tidy-path test/api_test_app + ./lint.sh --format-path include/firebolt ./lint.sh --no-build --build-dir build-dev EOF } @@ -80,9 +90,30 @@ while [[ $# -gt 0 ]]; do CLANG_TIDY_PATHS+=("${2:-}") shift ;; + --format-path) + if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then + echo "Missing value for --format-path" >&2 + usage + exit 1 + fi + CLANG_FORMAT_PATHS+=("${2:-}") + shift + ;; --fix) APPLY_FIXES=true ;; + --format-fix) + FORMAT_FIX=true + RUN_CLANG_FORMAT=true + ;; + --format-only) + RUN_CLANG_FORMAT=true + RUN_CLANG_TIDY=false + RUN_CPPCHECK=false + ;; + --no-format) + RUN_CLANG_FORMAT=false + ;; --tidy-only) RUN_CLANG_TIDY=true RUN_CPPCHECK=false @@ -106,13 +137,13 @@ done cd "$ROOT_DIR" -if [[ "$NO_BUILD" == false && "$BUILD_DIR" != "build-dev" ]]; then +if [[ "$RUN_CLANG_TIDY" == true && "$NO_BUILD" == false && "$BUILD_DIR" != "build-dev" ]]; then echo "--build-dir is only supported with --no-build (build step always uses build-dev)." >&2 exit 1 fi -if [[ "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then - echo "Nothing to run: both clang-tidy and cppcheck are disabled." >&2 +if [[ "$RUN_CLANG_FORMAT" == false && "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then + echo "Nothing to run: clang-format, clang-tidy, and cppcheck are all disabled." >&2 exit 1 fi @@ -121,6 +152,11 @@ if [[ "$APPLY_FIXES" == true && "$RUN_CLANG_TIDY" == false ]]; then exit 1 fi +if [[ "$FORMAT_FIX" == true && "$RUN_CLANG_FORMAT" == false ]]; then + echo "--format-fix requires clang-format to be enabled (remove --no-format)." >&2 + exit 1 +fi + if [[ "$RUN_CLANG_TIDY" == true ]] && ! command -v clang-tidy >/dev/null 2>&1; then echo "clang-tidy not found. Install it (e.g. apt install clang-tidy)." >&2 exit 1 @@ -131,19 +167,74 @@ if [[ "$RUN_CPPCHECK" == true ]] && ! command -v cppcheck >/dev/null 2>&1; then exit 1 fi +if [[ "$RUN_CLANG_FORMAT" == true ]] && ! command -v clang-format >/dev/null 2>&1; then + echo "clang-format not found. Install it (e.g. apt install clang-format)." >&2 + exit 1 +fi + if [[ "$CLEAN" == true ]]; then rm -rf "$BUILD_DIR" fi -if [[ "$NO_BUILD" == false ]]; then +if [[ "$NO_BUILD" == false && "$RUN_CLANG_TIDY" == true ]]; then ./build.sh +tests fi -if [[ ! -f "$BUILD_DIR/compile_commands.json" ]]; then +if [[ "$RUN_CLANG_TIDY" == true && ! -f "$BUILD_DIR/compile_commands.json" ]]; then echo "Missing $BUILD_DIR/compile_commands.json. Run ./build.sh +tests first." >&2 exit 1 fi +if [[ "$RUN_CLANG_FORMAT" == true ]]; then + if [[ "$FORMAT_FIX" == true ]]; then + echo "[lint] Running clang-format with fixes enabled" + else + echo "[lint] Running clang-format check" + fi + + format_paths=() + for p in "${CLANG_FORMAT_PATHS[@]}"; do + if [[ -e "$p" ]]; then + format_paths+=("$p") + fi + done + + if [[ ${#format_paths[@]} -eq 0 ]]; then + echo "No valid clang-format paths found." >&2 + exit 1 + fi + + mapfile -t format_files < <( + find "${format_paths[@]}" -type f \( -name "*.h" -o -name "*.hh" -o -name "*.hpp" -o -name "*.hxx" -o -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.cxx" \) | sort + ) + + if [[ ${#format_files[@]} -eq 0 ]]; then + echo "No C/C++ files found for clang-format." >&2 + exit 1 + fi + + clang_format_failed=0 + total_format_files=${#format_files[@]} + format_index=0 + for f in "${format_files[@]}"; do + format_index=$((format_index + 1)) + echo "[lint][clang-format] ${format_index}/${total_format_files}: $f" + if [[ "$FORMAT_FIX" == true ]]; then + clang-format -i "$f" + else + if ! clang-format --dry-run --Werror "$f"; then + clang_format_failed=1 + fi + fi + done + + if [[ "$FORMAT_FIX" == false && $clang_format_failed -ne 0 ]]; then + echo "clang-format reported issues." >&2 + echo "Run ./lint.sh --format-fix to apply formatting automatically." >&2 + exit 1 + fi +fi + if [[ "$RUN_CLANG_TIDY" == true ]]; then if [[ "$APPLY_FIXES" == true ]]; then echo "[lint] Running clang-tidy with fixes enabled" diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index edb3d35..431496f 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -24,28 +24,34 @@ #include #include -namespace Firebolt::Actions { +namespace Firebolt::Actions +{ ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) - : helper_(helper) - , subscriptionManager_(helper, this) -{} + : helper_(helper), + subscriptionManager_(helper, this) +{ +} -Result ActionsImpl::intent(const std::string& intent) const { +Result ActionsImpl::intent(const std::string& intent) const +{ nlohmann::json params; params["intent"] = intent; return helper_.invoke("Actions.intent", params); } -Result ActionsImpl::subscribeOnIntent(std::function&& notification) { +Result ActionsImpl::subscribeOnIntent(std::function&& notification) +{ return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); } -Result ActionsImpl::unsubscribe(SubscriptionId id) { +Result ActionsImpl::unsubscribe(SubscriptionId id) +{ return subscriptionManager_.unsubscribe(id); } -void ActionsImpl::unsubscribeAll() { +void ActionsImpl::unsubscribeAll() +{ subscriptionManager_.unsubscribeAll(); } diff --git a/src/actions_impl.h b/src/actions_impl.h index 157c88f..e7eaf61 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -25,9 +25,11 @@ #include "firebolt/actions.h" #include -namespace Firebolt::Actions { +namespace Firebolt::Actions +{ -class ActionsImpl : public IActions { +class ActionsImpl : public IActions +{ public: explicit ActionsImpl(Firebolt::Helpers::IHelper& helper); ActionsImpl(const ActionsImpl&) = delete; diff --git a/src/firebolt.cpp b/src/firebolt.cpp index 88f945f..0afad53 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -18,8 +18,8 @@ #include "firebolt/firebolt.h" #include "accessibility_impl.h" -#include "advertising_impl.h" #include "actions_impl.h" +#include "advertising_impl.h" #include "device_impl.h" #include "discovery_impl.h" #include "display_impl.h" diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 6025350..60c76ce 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -23,14 +23,16 @@ #define FIREBOLT_ACTIONS_JSON_H #pragma once -#include -#include -#include #include "firebolt/actions.h" +#include +#include +#include -namespace Firebolt::Actions { +namespace Firebolt::Actions +{ -namespace JsonData { +namespace JsonData +{ } // namespace JsonData diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index aff810c..c157be9 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -57,4 +57,3 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id.value()); verifyUnsubscribeResult(result); } - diff --git a/test/unit/actionsGeneratedTest.cpp b/test/unit/actionsGeneratedTest.cpp index 4c0b71e..86006f3 100644 --- a/test/unit/actionsGeneratedTest.cpp +++ b/test/unit/actionsGeneratedTest.cpp @@ -16,8 +16,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "mock_helper.h" #include "actions_impl.h" +#include "mock_helper.h" #include class ActionsGeneratedUTest : public ::testing::Test @@ -34,24 +34,23 @@ TEST_F(ActionsGeneratedUTest, Constructs) TEST_F(ActionsGeneratedUTest, UnsubscribeForwardsToHelper) { - EXPECT_CALL(mockHelper, unsubscribe(7)) - .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + EXPECT_CALL(mockHelper, unsubscribe(7)).WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); auto result = impl.unsubscribe(7); ASSERT_TRUE(result) << "unsubscribe should return success when helper succeeds"; } - TEST_F(ActionsGeneratedUTest, ForwardsintentTransportErrors) { EXPECT_CALL(mockHelper, invoke("Actions.intent", ::testing::_)) - .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& params) { - EXPECT_TRUE(params.is_object()) << "Expected params object for method call"; - EXPECT_TRUE(params.contains("intent")) << "Missing expected param key: intent"; - return Firebolt::Result{Firebolt::Error::General}; - })); + .WillOnce(::testing::Invoke( + [](const std::string& /*method*/, const nlohmann::json& params) + { + EXPECT_TRUE(params.is_object()) << "Expected params object for method call"; + EXPECT_TRUE(params.contains("intent")) << "Missing expected param key: intent"; + return Firebolt::Result{Firebolt::Error::General}; + })); auto result = impl.intent({}); EXPECT_FALSE(result) << "Expected error propagation when helper invoke fails"; } - diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 7444bd1..7295886 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -16,9 +16,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "actions_impl.h" #include "json_engine.h" #include "mock_helper.h" -#include "actions_impl.h" class ActionsUTest : public ::testing::Test, protected MockBase { @@ -51,4 +51,3 @@ TEST_F(ActionsUTest, SubscribeOnIntent) auto unsubResult = actionsImpl_.unsubscribe(*result); ASSERT_TRUE(unsubResult) << "ActionsImpl::unsubscribe() returned an error"; } - diff --git a/test/utils.cpp b/test/utils.cpp index 792bf4f..60de1be 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -125,7 +125,7 @@ void verifyUnsubscribeResult(const Firebolt::Result& result) FAIL() << "Unsubscribe failed." + toError(result); } } -void verifyEventReceived(std::mutex& mtx, std::condition_variable& cv, bool& eventReceived) +void verifyEventReceived(std::mutex& mtx, std::condition_variable& cv, const bool& eventReceived) { std::unique_lock lock(mtx); if (!cv.wait_for(lock, EventWaitTime, [&] { return eventReceived; })) @@ -134,7 +134,7 @@ void verifyEventReceived(std::mutex& mtx, std::condition_variable& cv, bool& eve } } -void verifyEventNotReceived(std::mutex& mtx, std::condition_variable& cv, bool& eventReceived) +void verifyEventNotReceived(std::mutex& mtx, std::condition_variable& cv, const bool& eventReceived) { // Wait for the event to be received or timeout after 5 seconds std::unique_lock lock(mtx); diff --git a/test/utils.h b/test/utils.h index bd1e2e3..b5d0693 100644 --- a/test/utils.h +++ b/test/utils.h @@ -32,8 +32,8 @@ void triggerRaw(const std::string& payload); void verifyEventSubscription(const Firebolt::Result& id); void verifyUnsubscribeResult(const Firebolt::Result& result); -void verifyEventReceived(std::mutex& mtx, std::condition_variable& cv, bool& eventReceived); -void verifyEventNotReceived(std::mutex& mtx, std::condition_variable& cv, bool& eventReceived); +void verifyEventReceived(std::mutex& mtx, std::condition_variable& cv, const bool& eventReceived); +void verifyEventNotReceived(std::mutex& mtx, std::condition_variable& cv, const bool& eventReceived); template inline std::string toError(const Firebolt::Result& result) { From 2a0b0b96a8fd8bc0af6fbccaf3670140fa87063e Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 27 May 2026 15:48:53 -0700 Subject: [PATCH 15/15] fix(review): tighten test safety and lint mode semantics --- lint.sh | 2 ++ test/component/actionsGeneratedTest.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/lint.sh b/lint.sh index 9487bd0..552384d 100755 --- a/lint.sh +++ b/lint.sh @@ -115,10 +115,12 @@ while [[ $# -gt 0 ]]; do RUN_CLANG_FORMAT=false ;; --tidy-only) + RUN_CLANG_FORMAT=false RUN_CLANG_TIDY=true RUN_CPPCHECK=false ;; --cppcheck-only) + RUN_CLANG_FORMAT=false RUN_CLANG_TIDY=false RUN_CPPCHECK=true ;; diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index c157be9..6ce5e5d 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -49,6 +49,7 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) cv.notify_one(); }); + ASSERT_TRUE(id) << toError(id); verifyEventSubscription(id); triggerEvent("Actions.onIntent", R"({"value":"launch"})");