diff --git a/.github/skills/bdd-feature-generator/SKILL.md b/.github/skills/bdd-feature-generator/SKILL.md new file mode 100644 index 000000000..053053eca --- /dev/null +++ b/.github/skills/bdd-feature-generator/SKILL.md @@ -0,0 +1,892 @@ +--- +name: bdd-feature-generator +description: Generate BDD (Behavior Driven Development) feature files from remote debugger source code analysis. Use for creating Gherkin-format documentation of RBUS event handling, static/dynamic profile processing, command execution, harmful command detection, log archive and upload, deep sleep handling, and uploadRRDLogs C API orchestration. Produces gap analysis between feature files and L2 test implementations. +--- + +# BDD Feature Generator for Remote Debugger + +## Purpose + +Automatically generate BDD feature files in Gherkin format by analyzing the remote debugger source code. This skill creates comprehensive behavioral documentation that can serve as: +- **Functional documentation** of daemon lifecycle, event handling, and profile processing +- **Test specifications** for L2 functional tests (`test/functional-tests/`) +- **Requirements traceability** linking source modules to observable behavior +- **Gap analysis baseline** for comparing L2 tests vs implemented behavior + +## Usage + +Invoke this skill when: +- Documenting existing remote debugger behaviors in BDD format +- Creating test specifications for new features (e.g., new profile types, upload mechanisms) +- Generating feature files for untested code paths (e.g., WebCfg events, deep sleep edge cases) +- Performing gap analysis between L2 tests and source implementation +- Onboarding new team members with behavioral documentation of the daemon + +## Project Context + +The remote debugger (`remotedebugger`) is an RDK component that enables remote collection, packaging, and upload of device diagnostic logs. It: +- Listens for **RBUS/TR-181 events** (IssueType triggers) to initiate debug data collection +- Processes **static profiles** (built-in JSON config at `/etc/rrd/remote_debugger.json`) and **dynamic profiles** (downloaded packages at `/media/apps/RDK-RRD-/etc/rrd/remote_debugger.json`) +- Executes diagnostic **commands** from profiles, with **sanity checks** against harmful commands +- Archives collected outputs as `.tgz` and **uploads** them to a remote server via `uploadRRDLogs.sh` or the **C API** (`rrd_upload_orchestrate`) +- Supports **append mode** combining commands from both static and dynamic profiles +- Handles **deep sleep** events for deferred processing +- Supports **category-only** issue types (all sub-nodes under a category) +- Enforces **single-instance** execution and **RFC enable/disable** control +- Runs as a **systemd service** with a main thread and a dedicated event-processing thread + +## Prerequisites + +Before running this skill: + +1. **Review the build system** — `Makefile.am`, `src/Makefile.am`, and `configure.ac` +2. **Identify compiled modules** — Core sources are always compiled; IARMBUS sources are conditional +3. **Review existing feature files** — Match the format in `test/functional-tests/features/` +4. **Understand test interfaces** — L2 tests use `rbuscli` (RBUS event trigger), log scraping (`/opt/logs/remotedebugger.log.0`), and file system checks (`/tmp/rrd/`) + +## Process + +### Step 1: Analyze Build Configuration + +The remote debugger build is Autotools-based. Identify compiled components from the Makefile chain: + +```bash +# Top-level: identifies src/ as the main SUBDIR +cat Makefile.am | grep "SUBDIRS" +# → SUBDIRS = src + +# Source level: identifies the remotedebugger binary and its sources +cat src/Makefile.am +# → bin_PROGRAMS = remotedebugger +# → remotedebugger_SOURCES = rrdMain.c rrdEventProcess.c rrdJsonParser.c +# rrdRunCmdThread.c rrdCommandSanity.c rrdDynamic.c rrdExecuteScript.c +# rrdMsgPackDecoder.c rrdInterface.c +# → if IARMBUS_ENABLE: +# remotedebugger_SOURCES += rrdIarmEvents.c uploadRRDLogs.c rrd_config.c +# rrd_sysinfo.c rrd_logproc.c rrd_archive.c rrd_upload.c +``` + +**Always compiled modules:** + +| Source File | Header | Purpose | +|---|---|---| +| `rrdMain.c` | `rrdMain.h` | Daemon entry, event thread, RFC enable check, message queue setup | +| `rrdEventProcess.c` | `rrdEventProcess.h` | IssueType/WebCfg/DeepSleep event dispatch, static/dynamic profile flow | +| `rrdJsonParser.c` | `rrdJsonParser.h` | JSON profile parsing, command/timeout extraction, issue node lookup | +| `rrdRunCmdThread.c` | `rrdRunCmdThread.h` | Command execution, output file management, result caching | +| `rrdCommandSanity.c` | `rrdCommandSanity.h` | Command validation against harmful command blocklist | +| `rrdDynamic.c` | `rrdDynamic.h` | Dynamic profile handling, deep sleep event processing | +| `rrdExecuteScript.c` | `rrdExecuteScript.h` | Upload debug output orchestration | +| `rrdMsgPackDecoder.c` | `rrdMsgPackDecoder.h` | WebConfig parameter decoding (MsgPack format) | +| `rrdInterface.c` | `rrdInterface.h` | RBUS registration, event handler setup, profile data elements | + +**Conditionally compiled modules (IARMBUS_ENABLE):** + +| Source File | Header | Purpose | +|---|---|---| +| `rrdIarmEvents.c` | — | IARM bus event handling (power state, deep sleep) | +| `uploadRRDLogs.c` | — | C implementation of upload orchestration entry point | +| `rrd_config.c` | `rrd_config.h` | Configuration loading (server URLs, paths, protocol, RFC/DCM) | +| `rrd_sysinfo.c` | `rrd_sysinfo.h` | System info (MAC address, timestamp, file/dir checks) | +| `rrd_logproc.c` | `rrd_logproc.h` | Log directory validation, preparation, live log handling | +| `rrd_archive.c` | `rrd_archive.h` | Archive creation (.tgz), filename generation, cleanup, CPU checks | +| `rrd_upload.c` | `rrd_upload.h` | Upload orchestration, lock handling, cleanup | + +**Shared headers (no implementation file):** + +| Header | Purpose | +|---|---| +| `rrdCommon.h` | Common constants, macros, data structures (data_buf, msgRRDHdr, etc.) | +| `rrdRbus.h` | RBUS event subscription, blob versioning | +| `rrd_log.h` | Logging subsystem initialization | + +**Exclude from feature generation:** +- `src/unittest/` — Unit tests (L1) +- `test/` — Test infrastructure +- `scripts/` — Runtime scripts (documented as behavior, not source) +- `docs/` — Existing documentation + +### Step 2: Analyze Source Code Structure + +For each compiled module: + +1. **Read the header file** (`.h`) — Identify public functions, data structures, constants +2. **Read the implementation** (`.c`) — Extract event flows, log messages, error paths +3. **Identify RBUS parameters/events** — Map registered data elements in `rrdInterface.c` +4. **Note conditional compilation** — `#ifdef IARMBUS_SUPPORT` gates upload and IARM modules +5. **Note key log messages** — Tests validate behavior by grepping these from log files +6. **Note file paths** — `/etc/rrd/remote_debugger.json`, `/tmp/rrd/`, `/media/apps/RDK-RRD-*` + +**Key elements to extract for each module:** + +| Element | Where to Find | Example | +|---|---|---| +| RBUS data elements | `rrdInterface.c` registration | `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType` | +| Event handlers | `rrdInterface.c` set handlers | `RRD_SET_ISSUE_EVENT` | +| Profile JSON paths | `rrdEventProcess.c`, `rrdDynamic.c` | `/etc/rrd/remote_debugger.json`, `/media/apps/RDK-RRD-{Node}/etc/rrd/remote_debugger.json` | +| Log messages | All `.c` files `RDK_LOG_*` calls | `"SUCCESS: Message sending Done"`, `"Json File parse Success..."` | +| Error conditions | `.c` return/log on failure | `"FAILED: Json File parse..."`, `"Harmful Command Found"` | +| File outputs | `rrdRunCmdThread.c` | `/tmp/rrd/{IssueType}/debug_outputs.txt` | +| Archive naming | `rrd_archive.c` | `{MAC}_{ISSUETYPE}_{TIMESTAMP}_RRD_DEBUG_LOGS.tgz` | +| Upload API | `rrd_upload.h` | `rrd_upload_orchestrate(upload_dir, issue_type)` | +| Compile guards | `.c` / `.h` `#ifdef` | `IARMBUS_SUPPORT` | + +### Step 3: Create Feature File Structure + +Feature files are placed in `test/functional-tests/features/`. + +**Naming convention for remote debugger:** + +| Behavior Area | Feature File | Description | +|---|---|---| +| Daemon startup | `rrd_start_subscribe_and_wait.feature` | RBUS subscription, event loop entry | +| RFC enable/disable | `rrd_start_control.feature` | RFC-controlled start/stop | +| Single instance | `rrd_single_instance.feature` | Prevents duplicate daemon instances | +| Static profile processing | `rrd_static_profile_report.feature` | End-to-end static profile flow | +| Static category report | `rrd_static_profile_category_report.feature` | Category-only issue type | +| Static profile with suffix | `test_rrd_static_profile_report_with_suffix.feature` | Suffixed issue type handling | +| Background command | `rrd_background_cmd_static_profile_report.feature` | Background command execution | +| Dynamic profile processing | `rrd_dynamic_profile_report.feature` | Dynamic profile fallback flow | +| Dynamic subcategory | `rrd_dynamic_profile_subcategory_report.feature` | Dynamic profile subcategory | +| Dynamic profile missing | `rrd_dynamic_profile_missing_report.feature` | Missing dynamic profile | +| Append mode | `rrd_append_report.feature` | Static+dynamic profile append | +| Append (static not found) | `rrd_append_dynamic_profile_static_not_found.feature` | Append when static missing | +| Harmful commands | `rrd_harmful_command_static_report.feature` | Sanity check blocks execution | +| Dynamic harmful | `test_rrd_dynamic_profile_harmful_report.feature` | Harmful command in dynamic profile | +| Corrupted profile | `rrd_corrupted_static_profile_report.feature` | Invalid/corrupted JSON handling | +| Static missing command | `rrd_static_profile_missing_command_report.feature` | Missing command in profile | +| Empty issue type | `rrd_empty_issuetype_event.feature` | Empty event value handling | +| Deep sleep | `rrd_deepsleep_static_report.feature` | Deep sleep issue type handling | +| Debug report upload | `rrd_debug_report_upload.feature` | Full upload + download validation | +| C API upload | `rrd_c_api_upload.feature` | `rrd_upload_orchestrate` C API tests | +| Suffix negative case | `test_rrd_static_profile_report_with_suffix_negative_case.feature` | Invalid suffix handling | + +### Step 4: Generate Feature Files + +Use this template for remote debugger feature files: + +```gherkin +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright [YEAR] RDK Management +# +# 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. +########################################################################## + +# Source: src/{SourceFile}.c + +Feature: Remote Debugger {Behavior Description} + + Scenario: {Prerequisite Check} + Given the configuration file path is set + When I check if the configuration file exists + Then the configuration file should exist + + Scenario: Verify remote debugger process is running + Given the remote debugger process is not running + When I start the remote debugger process + Then the remote debugger process should be running + + Scenario: {Main Behavior Scenario} + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" + Then {expected behavior validated via log scraping} +``` + +### Step 5: Map Source Code to Scenarios + +**For each remote debugger behavior, create scenarios covering:** + +1. **Prerequisites** — Config file exists, output directory exists, daemon not already running +2. **Daemon startup** — Process starts, RBUS subscription succeeds, event loop enters wait +3. **Event trigger** — RBUS IssueType set via `rbuscli`, message queue send/receive +4. **Profile parsing** — JSON read, parse success/failure, issue node lookup +5. **Command execution** — Sanity check, command run, output file creation +6. **Service management** — systemd service start/stop, journalctl collection +7. **Upload flow** — Archive creation, upload script invocation, success/failure +8. **Error/edge cases** — Harmful commands, empty events, corrupted profiles, missing profiles + +**Example mapping — RBUS event trigger (rrdInterface.c → rrdEventProcess.c):** + +```c +// Source: src/rrdInterface.c — RBUS set handler for IssueType +// Sends message to event thread via message queue +// Source: src/rrdEventProcess.c — Event thread receives and dispatches +``` + +**Generated scenarios:** + +```gherkin +Scenario: Send WebPA event for IssueType and verify message flow + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" + Then the event for RRD_SET_ISSUE_EVENT should be received + And the logs should contain "SUCCESS: Message sending Done" + And the logs should be seen with "SUCCESS: Message Reception Done" +``` + +**Example mapping — Static profile processing (rrdEventProcess.c → rrdJsonParser.c):** + +```gherkin +Scenario: Process static profile for issue type + When the remotedebugger received the message from webPA event + Then remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse success + And the issue data node and sub-node should be found in the JSON file + And the directory should be created to store the executed output + And Sanity check to validate the commands should be executed + And Command output should be added to the output file +``` + +**Example mapping — Dynamic profile fallback (rrdDynamic.c):** + +```gherkin +Scenario: Verify the Issuetype in dynamic path + Given the remote debugger issuetype is missing in static profile + When the remotedebugger read the json file form the dynamic path + Then remotedebugger json read and parse should be success + And remotedebugger should read the Issuetype from dynamic profile + And the issue data node and sub-node should be found in the JSON file +``` + +**Example mapping — Harmful command detection (rrdCommandSanity.c):** + +```gherkin +Scenario: Check for harmful commands and abort + Given remote debugger parse the static json profile successfully + When the issue command and the sanity commands are matched + Then the remote debugger should exit the processing of commands + And Abort the command execution and skip report upload +``` + +**Example mapping — Upload orchestration C API (rrd_upload.c):** + +```gherkin +Scenario: Validate rrd_upload_orchestrate C API with valid parameters + Given the remote debugger is configured + And test log files are created in the upload directory + When I call rrd_upload_orchestrate with valid upload directory and issue type + Then the C API should return success code 0 + And logs should contain "Configuration loaded" + And logs should contain "MAC:" for MAC address + And logs should contain "Log directory validated and prepared" + And logs should contain "Issue type sanitized" + And logs should contain "Archive filename:" with the generated filename + And logs should contain "Creating" for tarfile creation + And logs should contain "Invoking uploadSTBLogs binary to upload" +``` + +**Example mapping — Single instance enforcement (rrdMain.c):** + +```gherkin +Scenario: Remote debugger exits if another instance is invoked + Given the RemoteDebugger is not already running + When the RemoteDebugger binary is invoked + Then the RemoteDebugger should be started + And when the RemoteDebugger is attempted to be started again + Then the RemoteDebugger should not start another instance +``` + +### Step 6: Document Issue Type Variations + +For issue types with multiple forms, use individual scenarios: + +```gherkin +Scenario: Static profile with Node.SubNode issue type + Given the remote debugger is running + When I trigger IssueType "Device.Info" + Then the issue data node "Device" and sub-node "Info" should be found in the JSON file + +Scenario: Category-only issue type (all sub-nodes) + Given the remote debugger is running + When I trigger IssueType "Device" + Then all sub-nodes under category "Device" should be processed + +Scenario: Suffixed issue type + Given the remote debugger is running + When I trigger IssueType "Device.Info_ab1bghjh" + Then the base issue "Device.Info" should be matched in the profile + And the archive filename should include the full suffixed issue type +``` + +### Step 7: Create README Index + +Create or update `test/functional-tests/features/README.md`: + +```markdown +# Remote Debugger Feature Documentation + +This folder contains BDD feature files documenting the remote debugger +daemon behavior implemented in `src/`. + +## Feature Files Overview + +| Feature File | Source Components | Description | +|---|---|---| +| `rrd_start_subscribe_and_wait.feature` | `rrdMain.c`, `rrdInterface.c` | RBUS subscription, event loop | +| `rrd_start_control.feature` | `rrdMain.c` | RFC enable/disable control | +| `rrd_single_instance.feature` | `rrdMain.c` | Single instance enforcement | +| `rrd_static_profile_report.feature` | `rrdEventProcess.c`, `rrdJsonParser.c`, `rrdRunCmdThread.c` | Static profile end-to-end | +| `rrd_static_profile_category_report.feature` | `rrdEventProcess.c`, `rrdJsonParser.c` | Category-only issue type | +| `rrd_dynamic_profile_report.feature` | `rrdDynamic.c`, `rrdJsonParser.c` | Dynamic profile fallback | +| `rrd_dynamic_profile_subcategory_report.feature` | `rrdDynamic.c` | Dynamic subcategory | +| `rrd_append_report.feature` | `rrdDynamic.c`, `rrdEventProcess.c` | Append mode (static+dynamic) | +| `rrd_harmful_command_static_report.feature` | `rrdCommandSanity.c` | Harmful command blocking | +| `rrd_corrupted_static_profile_report.feature` | `rrdJsonParser.c` | Corrupted JSON handling | +| `rrd_empty_issuetype_event.feature` | `rrdEventProcess.c` | Empty event value | +| `rrd_deepsleep_static_report.feature` | `rrdDynamic.c`, `rrdIarmEvents.c` | Deep sleep handling | +| `rrd_debug_report_upload.feature` | `rrdExecuteScript.c`, `uploadRRDLogs.sh` | Upload + download validation | +| `rrd_c_api_upload.feature` | `rrd_upload.c`, `rrd_config.c`, `rrd_archive.c` | C API upload orchestration | + +## Source Module Mapping + +Based on `src/Makefile.am` and `configure.ac`: + +### Always Compiled +- `rrdMain.c` — Daemon lifecycle, event thread, RFC enable check +- `rrdEventProcess.c` — IssueType/WebCfg event dispatch +- `rrdJsonParser.c` — JSON profile parsing, command extraction +- `rrdRunCmdThread.c` — Command execution, output management +- `rrdCommandSanity.c` — Harmful command validation +- `rrdDynamic.c` — Dynamic profile handling, deep sleep +- `rrdExecuteScript.c` — Upload debug output orchestration +- `rrdMsgPackDecoder.c` — WebConfig MsgPack decoding +- `rrdInterface.c` — RBUS registration, event handlers + +### Conditionally Compiled (IARMBUS_ENABLE) +- `rrdIarmEvents.c` — IARM bus events (power state) +- `uploadRRDLogs.c` — C upload orchestration entry point +- `rrd_config.c` — Configuration loading (RFC/DCM/fallback) +- `rrd_sysinfo.c` — System info (MAC, timestamp) +- `rrd_logproc.c` — Log directory validation/preparation +- `rrd_archive.c` — Archive creation (.tgz) +- `rrd_upload.c` — Upload orchestration, lock handling + +## RBUS Data Elements + +| Data Element | Type | Handler | +|---|---|---| +| `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType` | SET event | Triggers debug data collection | +| `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.WebCfgData` | SET event | WebConfig-based trigger | +| `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.setProfileData` | SET | Profile data injection | +| `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.getProfileData` | GET | Profile data retrieval | + +## Test Interface Summary + +| Interface | Tool | Log File | Used For | +|---|---|---|---| +| RBUS event | `rbuscli set` | `/opt/logs/remotedebugger.log.0` | IssueType trigger | +| Log scraping | Python `grep_rrdlogs()` | `/opt/logs/remotedebugger.log.0` | Behavior validation | +| File system | Python `os.path`, `subprocess` | `/tmp/rrd/`, `/etc/rrd/` | Config/output checks | +| Upload | Mock xconf server | — | Archive upload validation | +| Process control | `pidof`, `kill`, `nohup` | — | Daemon start/stop | + +## Generation Date + +Generated: {DATE} +``` + +## Scenario Patterns for Remote Debugger + +### Prerequisite Check Pattern + +```gherkin +Scenario: Check if remote debugger configuration file exists + Given the configuration file path is set + When I check if the configuration file exists + Then the configuration file should exist + +Scenario: Check if /tmp/rrd output directory exists + Given the /tmp/rrd directory path is set + When I check if the /tmp/rrd directory exists + Then the /tmp/rrd directory should exist +``` + +### Daemon Startup Pattern + +```gherkin +Scenario: Verify remote debugger process is running + Given the remote debugger process is not running + When I start the remote debugger process + Then the remote debugger process should be running + +Scenario: Remote debugger should subscribe to events + Given the remote debugger binary is invoked + When the remote debugger is started + Then the remote debugger should subscribe to rbus and wait for the events + And the log file should contain "SUCCESS: RBUS Event Subscribe for RRD done!" + And the log file should contain "Waiting for TR69/RBUS Events..." +``` + +### RFC Enable/Disable Pattern + +```gherkin +Scenario: Remote Debugger Starts when Enabled + Given RFC Value for RDKRemoteDebugger.Enable is enabled + When the remotedebugger is started check the value of the RFC parameter + And the RDKRemoteDebugger Enable value is true + Then the remotedebugger should be started and running as daemon + +Scenario: Remote Debugger Stops when Disabled + Given RFC Value for RDKRemoteDebugger.Enable is disabled + When the remotedebugger is started check the value of the RFC parameter + And the RDKRemoteDebugger Enable value is false + Then the remotedebugger must be stopped and process should not be running +``` + +### RBUS Event Trigger Pattern + +```gherkin +Scenario: Send WebPA event for IssueType + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" + Then the event for RRD_SET_ISSUE_EVENT should be received + And the logs should contain "SUCCESS: Message sending Done" + And the logs should be seen with "SUCCESS: Message Reception Done" +``` + +### Static Profile Processing Pattern + +```gherkin +Scenario: Process static profile commands + When the remotedebugger received the message from webPA event + Then remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse success + And the issue data node and sub-node should be found in the JSON file + And the directory should be created to store the executed output + And Sanity check to validate the commands should be executed + And Command output should be added to the output file + And the issuetype systemd service should start successfully + And the journalctl service should start successfully + And the process should sleep with timeout + And the issuetype systemd service should stop successfully + And the remotedebugger should call script to upload the debug report +``` + +### Dynamic Profile Fallback Pattern + +```gherkin +Scenario: Verify the Issuetype is not found in static profile + Given the remote debugger received the message from RBUS command + When the remotedebugger static json profile is present + Then remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse Success + And remotedebugger should log as the Issue requested is not found in the profile + +Scenario: Verify the Issuetype in dynamic path + Given the remote debugger issuetype is missing in static profile + When the remotedebugger read the json file from the dynamic path + Then remotedebugger json read and parse should be success + And remotedebugger should read the Issuetype from dynamic profile + And the issue data node and sub-node should be found in the JSON file +``` + +### Append Mode Pattern + +```gherkin +Scenario: Verify append of static and dynamic profile commands + Given the remote debugger received the message from RBUS command + When the remotedebugger read the json file from the dynamic path + Then remotedebugger json read and parse should be success in dynamic path + And remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse Success + And remotedebugger should log as the Issue requested found in the profile + And Update the command after appending data from both profiles, then execute +``` + +### Harmful Command Detection Pattern + +```gherkin +Scenario: Check for harmful commands and abort + Given remote debugger parse the static json profile successfully + When the issue node and subnode are present in the profile + Then the remote debugger should read the Sanity Check list from profile + And the remotedebugger should perform sanity check on issue commands + Given the remote debugger profile has the harmful commands + When the issue command and the sanity commands are matched + Then the remote debugger should exit the processing of commands + And Abort the command execution and skip report upload +``` + +### Upload Flow Pattern + +```gherkin +Scenario: Upload remote debugger debug report + Given the remote debugger completed the command execution + When remotedebugger calls the uploadRRD.sh script + Then check for the tarfile is created in the output directory + And the file is uploaded to the mockxconf server + And the upload success logs are seen in the logs + +Scenario: Download the file from the mockxconf server + Given the remote debugger report upload success + When curl command is used to download the file + Then the curl command should return success + And the file should be downloaded successfully +``` + +### C API Upload Orchestration Pattern + +```gherkin +Scenario: Validate rrd_upload_orchestrate with valid parameters + Given the remote debugger is configured + And test log files are created in the upload directory + When I call rrd_upload_orchestrate with valid upload directory and issue type + Then the C API should return success code 0 + And logs should contain "Configuration loaded" + And logs should contain "MAC:" for MAC address + And logs should contain "Log directory validated and prepared" + And logs should contain "Issue type sanitized" + And logs should contain "Archive filename:" with the generated filename + And logs should contain "Invoking uploadSTBLogs binary to upload" + +Scenario: Test rrd_upload_orchestrate with NULL parameters + Given the remote debugger is configured + When I call rrd_upload_orchestrate with NULL upload directory + Then the C API should return error code 1 + And error logs should contain "Invalid parameters" + +Scenario: Test rrd_upload_orchestrate with empty directory + Given the remote debugger is configured + And the upload directory is empty + When I call rrd_upload_orchestrate with the empty directory + Then the C API should return error code 6 + And error logs should contain "Invalid or empty upload directory" +``` + +### Error/Edge Case Pattern + +```gherkin +Scenario: Corrupted JSON profile + When the remotedebugger received the message from webPA event + Then remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse Failed + +Scenario: Empty issue type event + Given the remote debugger is running + When I trigger the event with empty IssueType value + Then the remotedebugger receives the message from webPA event + And remotedebugger should log as not processing empty event + +Scenario: Missing issue type in static profile + When the remotedebugger received the message + Then remotedebugger should log as the Issue requested is not found in the profile +``` + +### Single Instance Pattern + +```gherkin +Scenario: Remote debugger exits if another instance is invoked + Given the RemoteDebugger is not already running + When the RemoteDebugger binary is invoked + Then the RemoteDebugger should be started + And when the RemoteDebugger is attempted to be started again + Then the RemoteDebugger should not start another instance +``` + +## Quality Checklist + +Before completing feature generation for remote debugger: + +- [ ] All source modules analyzed (`src/Makefile.am` sources list) +- [ ] Conditional modules noted with build flags (`IARMBUS_ENABLE`) +- [ ] Each RBUS event handler has at least one trigger scenario +- [ ] Static profile happy path has end-to-end scenario (trigger → parse → execute → upload) +- [ ] Dynamic profile fallback flow documented +- [ ] Append mode (static+dynamic) documented +- [ ] Harmful command sanity check scenarios included +- [ ] Corrupted/missing profile error scenarios documented +- [ ] Empty/invalid issue type edge cases documented +- [ ] Deep sleep event handling documented +- [ ] C API upload orchestration scenarios included (valid + error cases) +- [ ] RFC enable/disable control documented +- [ ] Single instance enforcement documented +- [ ] Suffixed issue type handling documented +- [ ] License headers included (Apache 2.0, RDK Management) +- [ ] Source file references included as comments +- [ ] Log message assertions match actual daemon log output +- [ ] Feature-to-test file mapping documented for gap analysis +- [ ] Scenarios are atomic (one behavior per scenario) +- [ ] Given/When/Then/And/Or structure followed consistently + +## Output Structure + +``` +test/functional-tests/ +├── features/ +│ ├── README.md # Index, module mapping, gap summary +│ ├── rrd_start_subscribe_and_wait.feature # RBUS subscription, event loop +│ ├── rrd_start_control.feature # RFC enable/disable +│ ├── rrd_single_instance.feature # Single instance enforcement +│ ├── rrd_static_profile_report.feature # Static profile end-to-end +│ ├── rrd_static_profile_category_report.feature # Category-only issue type +│ ├── test_rrd_static_profile_report_with_suffix.feature # Suffixed issue type +│ ├── test_rrd_static_profile_report_with_suffix_negative_case.feature # Invalid suffix +│ ├── rrd_background_cmd_static_profile_report.feature # Background command execution +│ ├── rrd_dynamic_profile_report.feature # Dynamic profile fallback +│ ├── rrd_dynamic_profile_subcategory_report.feature # Dynamic subcategory +│ ├── rrd_dynamic_profile_missing_report.feature # Missing dynamic profile +│ ├── rrd_append_report.feature # Append mode (static+dynamic) +│ ├── rrd_append_dynamic_profile_static_not_found.feature # Append when static missing +│ ├── rrd_harmful_command_static_report.feature # Harmful command blocking +│ ├── test_rrd_dynamic_profile_harmful_report.feature # Dynamic harmful commands +│ ├── rrd_corrupted_static_profile_report.feature # Invalid/corrupted JSON +│ ├── rrd_static_profile_missing_command_report.feature # Missing command in profile +│ ├── rrd_empty_issuetype_event.feature # Empty event value +│ ├── rrd_deepsleep_static_report.feature # Deep sleep handling +│ ├── rrd_debug_report_upload.feature # Upload + download validation +│ └── rrd_c_api_upload.feature # C API upload orchestration +└── tests/ + ├── helper_functions.py # Log grep, process control, constants + ├── test_rrd_static_profile_report.py # Static profile test + ├── test_rrd_dynamic_profile_report.py # Dynamic profile test + ├── test_rrd_c_api_upload.py # C API upload test + ├── test_rrd_single_instance.py # Single instance test + ├── ... # (see full listing below) + └── uploadSTBLogs.sh # Mock upload script +``` + +## Example: Complete Remote Debugger Feature File + +```gherkin +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 RDK Management +# +# 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. +########################################################################## + +# Source: src/rrdEventProcess.c +# Source: src/rrdJsonParser.c +# Source: src/rrdRunCmdThread.c +# Source: src/rrdCommandSanity.c + +Feature: Remote Debugger Static Report + + Scenario: Check if remote debugger configuration file exists + Given the configuration file path is set + When I check if the configuration file exists + Then the configuration file should exist + + Scenario: Check if /tmp/rrd output directory exists + Given the /tmp/rrd directory path is set + When I check if the /tmp/rrd directory exists + Then the /tmp/rrd directory should exist + + Scenario: Verify remote debugger process is running + Given the remote debugger process is not running + When I start the remote debugger process + Then the remote debugger process should be running + + Scenario: Send WebPA event for IssueType and verify end-to-end processing + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" + Then the event for RRD_SET_ISSUE_EVENT should be received + And the logs should contain "SUCCESS: Message sending Done" + And the logs should be seen with "SUCCESS: Message Reception Done" + When the remotedebugger received the message from webPA event + Then remotedebugger should read the Json file + And remotedebugger logs should contain the Json File Parse success + And the issue data node and sub-node should be found in the JSON file + And the directory should be created to store the executed output + And Sanity check to validate the commands should be executed + And Command output should be added to the output file + And the issuetype systemd service should start successfully + And the journalctl service should start successfully + And the process should sleep with timeout + And the issuetype systemd service should stop successfully + And the remotedebugger should call script to upload the debug report + + Scenario: Upload remote debugger debug report + When I check the upload status in the logs + Then the upload should be successful if upload is success + Or the upload should fail if upload fails +``` + +## Integration with Gap Analysis + +After generating feature files, use them for gap analysis against the L2 test suite: + +### Step 1: Map Features to Existing Tests + +``` +features/rrd_start_subscribe_and_wait.feature ↔ tests/test_rrd_start_subscribe_and_wait.py +features/rrd_start_control.feature ↔ tests/test_rrd_start_control.py +features/rrd_single_instance.feature ↔ tests/test_rrd_single_instance.py +features/rrd_static_profile_report.feature ↔ tests/test_rrd_static_profile_report.py +features/rrd_static_profile_category_report.feature ↔ tests/test_rrd_static_profile_category_report.py +features/rrd_background_cmd_static_profile_report.feature ↔ tests/test_rrd_background_cmd_static_profile_report.py +features/rrd_dynamic_profile_report.feature ↔ tests/test_rrd_dynamic_profile_report.py +features/rrd_dynamic_profile_subcategory_report.feature ↔ tests/test_rrd_dynamic_subcategory_report.py +features/rrd_dynamic_profile_missing_report.feature ↔ tests/test_rrd_dynamic_profile_missing_report.py +features/rrd_append_report.feature ↔ tests/test_rrd_append_report.py +features/rrd_append_dynamic_profile_static_not_found.feature ↔ tests/test_rrd_append_dynamic_profile_static_notfound.py +features/rrd_harmful_command_static_report.feature ↔ tests/test_rrd_harmful_command_static_report.py +features/test_rrd_dynamic_profile_harmful_report.feature ↔ tests/test_rrd_dynamic_profile_harmful_report.py +features/rrd_corrupted_static_profile_report.feature ↔ tests/test_rrd_corrupted_static_profile_report.py +features/rrd_static_profile_missing_command_report.feature ↔ tests/test_rrd_static_profile_missing_command_report.py +features/rrd_empty_issuetype_event.feature ↔ tests/test_rrd_empty_issuetype_event.py +features/rrd_deepsleep_static_report.feature ↔ tests/test_rrd_deepsleep_static_report.py +features/rrd_debug_report_upload.feature ↔ tests/test_rrd_debug_report_upload.py +features/rrd_c_api_upload.feature ↔ tests/test_rrd_c_api_upload.py +features/test_rrd_static_profile_report_with_suffix.feature ↔ tests/test_rrd_static_profile_report_with_suffix.py +features/test_rrd_static_profile_report_with_suffix_negative_case.feature ↔ tests/test_rrd_static_profile_report_with_suffix_negative_case.py +``` + +### Step 2: Count Coverage + +For each feature file: +1. Count total scenarios (= total testable behaviors) +2. Count scenarios that have a matching `test_*` function in `test/functional-tests/tests/` +3. Calculate coverage = matched / total + +### Step 3: Identify Missing Tests + +Features without test coverage fall into categories: + +| Category | Example | Required Infrastructure | +|---|---|---| +| WebCfg event handling | WebCfgData RBUS event trigger | WebConfig mock, MsgPack payload | +| Profile data SET/GET | setProfileData / getProfileData | `rbuscli` SET/GET validation | +| Upload lock contention | Concurrent upload attempts | Lock file manipulation | +| Deep sleep edge cases | Deep sleep during active collection | IARM event simulation | +| Archive CPU throttle | CPU usage too high during archive | CPU load simulation | +| Configuration fallback | RFC → DCM → dcm.properties chain | Config file manipulation | + +### Step 4: Identify Undocumented Tests + +Tests that exist in `test/functional-tests/tests/` but have no matching scenario in the +`test/functional-tests/features/` files. These should be documented retroactively: + +- `test_rrd_profile_data.py` — Profile data SET/GET (no matching `.feature`) + +### Step 5: Generate Gap Report + +Include a summary table in `test/functional-tests/features/README.md`: + +```markdown +| Behavior Area | Feature Scenarios | L2 Tests | Coverage | Top Gaps | +|---|:---:|:---:|:---:|---| +| Daemon startup/subscribe | 3 | 3 | 100% | — | +| RFC enable/disable | 2 | 2 | 100% | — | +| Single instance | 1 | 1 | 100% | — | +| Static profile report | 5 | 5 | 100% | — | +| Static category report | 5 | 5 | 100% | — | +| Dynamic profile report | 5 | 5 | 100% | — | +| Append mode | 4 | 4 | 100% | — | +| Harmful commands | 4 | 4 | 100% | — | +| Corrupted profile | 4 | 4 | 100% | — | +| Empty issuetype | 2 | 2 | 100% | — | +| Deep sleep | 5 | 5 | 100% | — | +| Debug report upload | 7 | 7 | 100% | — | +| C API upload | 8+ | 8+ | ~100% | Error path coverage | +| WebCfg event | 0 | 0 | 0% | Entire flow | +| Profile data SET/GET | 0 | 1 | partial | No feature file | +| Upload lock contention | 0 | 0 | 0% | Concurrency tests | +``` + +## Current L2 Test Layout + +``` +test/functional-tests/ +├── features/ # BDD feature files (documentation + test specs) +│ ├── rrd_start_subscribe_and_wait.feature # RBUS subscription +│ ├── rrd_start_control.feature # RFC enable/disable +│ ├── rrd_single_instance.feature # Single instance +│ ├── rrd_static_profile_report.feature # Static profile end-to-end +│ ├── rrd_static_profile_category_report.feature +│ ├── test_rrd_static_profile_report_with_suffix.feature +│ ├── test_rrd_static_profile_report_with_suffix_negative_case.feature +│ ├── rrd_background_cmd_static_profile_report.feature +│ ├── rrd_dynamic_profile_report.feature +│ ├── rrd_dynamic_profile_subcategory_report.feature +│ ├── rrd_dynamic_profile_missing_report.feature +│ ├── rrd_append_report.feature +│ ├── rrd_append_dynamic_profile_static_not_found.feature +│ ├── rrd_harmful_command_static_report.feature +│ ├── test_rrd_dynamic_profile_harmful_report.feature +│ ├── rrd_corrupted_static_profile_report.feature +│ ├── rrd_static_profile_missing_command_report.feature +│ ├── rrd_empty_issuetype_event.feature +│ ├── rrd_deepsleep_static_report.feature +│ ├── rrd_debug_report_upload.feature +│ └── rrd_c_api_upload.feature +├── tests/ # Runnable pytest functions +│ ├── helper_functions.py # Log grep, process control, file checks, constants +│ ├── test_rrd_start_subscribe_and_wait.py +│ ├── test_rrd_start_control.py +│ ├── test_rrd_single_instance.py +│ ├── test_rrd_static_profile_report.py +│ ├── test_rrd_static_profile_category_report.py +│ ├── test_rrd_static_profile_report_with_suffix.py +│ ├── test_rrd_static_profile_report_with_suffix_negative_case.py +│ ├── test_rrd_background_cmd_static_profile_report.py +│ ├── test_rrd_dynamic_profile_report.py +│ ├── test_rrd_dynamic_subcategory_report.py +│ ├── test_rrd_dynamic_profile_missing_report.py +│ ├── test_rrd_append_report.py +│ ├── test_rrd_append_dynamic_profile_static_notfound.py +│ ├── test_rrd_harmful_command_static_report.py +│ ├── test_rrd_dynamic_profile_harmful_report.py +│ ├── test_rrd_corrupted_static_profile_report.py +│ ├── test_rrd_static_profile_missing_command_report.py +│ ├── test_rrd_empty_issuetype_event.py +│ ├── test_rrd_deepsleep_static_report.py +│ ├── test_rrd_debug_report_upload.py +│ ├── test_rrd_c_api_upload.py +│ ├── test_rrd_profile_data.py # ⚠ No matching .feature file +│ ├── create_json.sh # JSON profile creation helper +│ ├── deepsleep_main.c # Deep sleep simulation binary +│ ├── power_controller.h # Power controller mock header +│ ├── Makefile # Test build file +│ └── uploadSTBLogs.sh # Mock upload script +``` + +**Test runner:** `pytest`, executed sequentially per test file. +**Interfaces exercised:** `rbuscli` (RBUS event trigger), log scraping (`/opt/logs/remotedebugger.log.0`), file system checks (`/tmp/rrd/`, `/etc/rrd/`), mock xconf server (upload validation). + +## Maintenance + +When remote debugger source code changes: + +1. **New RBUS event/parameter added** — Add scenario to appropriate `.feature` file; update RBUS data elements table in README +2. **New profile processing mode** — Create a new `.feature` file; add to README index +3. **New upload mechanism** — Document the upload flow; add C API test scenarios if applicable +4. **Handler removed** — Remove corresponding scenario; note in gap analysis +5. **Build flag changed** — Update conditional compilation notes in README +6. **L2 test added** — Update gap analysis coverage numbers; create `.feature` file if missing +7. **New log messages added** — Update log assertion strings in relevant feature scenarios +8. **New sanity check rules** — Add harmful command detection scenarios +9. **Version tag** — Include generation date in README + +## Related Skills + +- `technical-documentation-writer` — For detailed architecture and API docs (`docs/`) +- `memory-safety-analyzer` — For safety analysis of C source code +- `thread-safety-analyzer` — For concurrency analysis of event thread and message queue +- `quality-checker` — For running static analysis and build verification +- `triage-logs` — For correlating device logs with remote debugger source code diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9247755..6e4641101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.3.4](https://github.com/rdkcentral/remote_debugger/compare/1.3.3...1.3.4) + +- RDK-60236 : Remote Debugger Supports UUID Information in Debug Report File [`#194`](https://github.com/rdkcentral/remote_debugger/pull/194) +- Refactor issueTypeSplitter to include suffix handling [`c56fef3`](https://github.com/rdkcentral/remote_debugger/commit/c56fef33df07f47c2ea91c26010548bf0a3baa36) +- Merge tag '1.3.3' into develop [`7151d20`](https://github.com/rdkcentral/remote_debugger/commit/7151d208ced521de7ee7dee66cca6aea951e95b3) + #### [1.3.3](https://github.com/rdkcentral/remote_debugger/compare/1.3.2...1.3.3) +> 23 April 2026 + - RDK-59833 : Remote Debugger datamodel support to retrieve Static Profile Data [`#183`](https://github.com/rdkcentral/remote_debugger/pull/183) - RDKEMW-16897: Add Agentic Support for the RRD [`#182`](https://github.com/rdkcentral/remote_debugger/pull/182) +- RRD 1.3.3 release changelog updates [`bdaea9a`](https://github.com/rdkcentral/remote_debugger/commit/bdaea9aa695da5b0c0450cddd096052bfd5b0ce3) - Merge tag '1.3.2' into develop [`8f793e6`](https://github.com/rdkcentral/remote_debugger/commit/8f793e69b6b2b91a27c6a8148014207788503efa) #### [1.3.2](https://github.com/rdkcentral/remote_debugger/compare/1.3.1...1.3.2) diff --git a/docs/rrd_uuid_support_implementation_HLD.md b/docs/rrd_uuid_support_implementation_HLD.md new file mode 100644 index 000000000..45ff6ca3d --- /dev/null +++ b/docs/rrd_uuid_support_implementation_HLD.md @@ -0,0 +1,207 @@ +# Remote Debugger Supports UUID Information in Debug Report File — High-Level Design Document + +## Document Information + +| Field | Value | +|-------------------|-----------------------------------------| +| **Feature** | Supports UUID Information in Debug Report File | +| **Component** | Remote Debugger (RRD) | +| **Version** | 1.0 | +| **Date** | May 2026 | +| **Target Platform** | Embedded Linux Systems | + +--- + +## 1. Executive Summary + +This document describes the high-level design for adding support for an **optional, underscore-delimited suffix** appended to the `IssueType` RFC value (e.g., `Device.DeviceInfo_ab1234`). The suffix is parsed, validated, sanitized, and carried through the entire event-processing pipeline so that it can be re-appended to the generated upload archive filename. Additionally, systemd unit names are disambiguated using an epoch timestamp, and the debug output file open-mode is corrected to prevent stale data accumulation. + +--- + +## 2. Background and Motivation + +### 2.1 Problem Statement + +Previously, the `IssueType` RFC parameter was expected to contain a simple, period-delimited string (e.g., `Device.DeviceInfo`). Operators needed a way to attach additional context (e.g., a ticket number or short identifier) directly to the event value so that the resulting debug archive could be uniquely correlated with an external issue tracker without modifying the core node/sub-node classification logic. + +### 2.2 Goals + +1. Parse and validate an optional `_` token appended to the IssueType string. +2. Sanitize the suffix to prevent shell injection when it is embedded in filenames or commands. +3. Propagate the suffix through the event pipeline to the archive upload step. +4. Append the suffix to the upload archive filename so the remote server can correlate the archive with the originating issue context. +5. Disambiguate systemd transient unit names to avoid conflicts when the same IssueType is processed concurrently or repeatedly. + +### 2.3 Non-Goals + +- Changing the node/sub-node classification logic. +- Supporting suffixes longer than 9 characters (including the leading `_`). +- Modifying the upstream RFC parameter interface. + +--- + +## 3. Architecture Overview + +### 3.1 System Context + +```mermaid +graph TB + subgraph Device["Embedded Device"] + RFC["RFC/RBUS Event
IssueType = Device.Info_ab1234"] + subgraph RRD["Remote Debugger (RRD) Process"] + Interface["rrdInterface
Event Reception & Routing"] + EventProc["rrdEventProcess
IssueType Processing"] + JsonParser["rrdJsonParser
split_issue_type() + JSON Lookup"] + CmdThread["rrdRunCmdThread
Command Execution"] + LogProc["rrd_logproc
Log Conversion"] + Archive["rrd_archive
Tar/Gz Creation"] + Upload["rrd_upload
S3/HTTP Upload"] + end + Interface -->|"data_buf (mdata, suffix)"| EventProc + EventProc -->|"data_buf"| JsonParser + JsonParser -->|"issueData + suffix"| CmdThread + CmdThread -->|"debug_outputs.txt"| Archive + Archive -->|".tar.gz"| Upload + end + Upload -->|"HTTPS"| RemoteServer["Remote Log Server"] +``` + +### 3.2 Module Responsibilities + +| Module | File(s) | Responsibility | +|--------|---------|----------------| +| Event Interface | `rrdInterface.c` | Receives RBUS/RFC events; initialises and de-allocates `data_buf`; propagates `suffix = NULL` initially | +| Event Processor | `rrdEventProcess.c` | Splits IssueType list; calls `split_issue_type()`; populates `data_buf.suffix`; routes to JSON parser | +| JSON Parser | `rrdJsonParser.c` | Implements `split_issue_type()`; resolves node/sub-node; builds upload filename with suffix | +| Command Thread | `rrdRunCmdThread.c` | Executes debug commands via systemd-run; writes `debug_outputs.txt` (w+ mode); appends epoch to unit name | +| Log Processor | `rrd_logproc.c` | Converts IssueType to safe filename characters; preserves hyphens for suffix UUID tokens | +| Archive Manager | `rrd_archive.c` | Creates `.tar.gz` archive of the output directory | +| Upload Manager | `rrd_upload.c` | Uploads the archive to the remote log server | +| Common Types | `rrdCommon.h` | Defines `data_buf` struct with new `suffix` field | + +--- + +## 4. Data Flow + +### 4.1 Nominal Flow (with suffix) + +``` +RFC set: Device.DeviceInfo_ab1234 + | + v +rrdInterface._remoteDebuggerEventHandler() + → RRD_data_buff_init() [suffix = NULL] + → pushIssueTypesToMsgQueue("Device.DeviceInfo_ab1234", EVENT_MSG) + | + v +rrdEventProcess.processIssueTypeEvent() + → issueTypeSplitter("Device.DeviceInfo_ab1234", ',', &cmdMap) + → for each token: split_issue_type(token, base, suffix) + base = "Device.DeviceInfo" + suffix = "_ab1234" + → cmdBuff->mdata = "Device.DeviceInfo" + → cmdBuff->suffix = "_ab1234" + → processIssueType(cmdBuff) + | + v +rrdJsonParser.checkIssueNodeInfo() + → Reads JSON profile for "Device.DeviceInfo" + → outdir = "/tmp/rrd/DeviceInfo-DebugReport-2026-05-13-10-00-00" + → executeCommands() / invokeSanityandCommandExec() + | + v +rrdRunCmdThread.executeCommands() + → unit = "remote_debugger_DeviceInfo_" + → systemd-run --unit=remote_debugger_DeviceInfo_ ... + → journalctl -u remote_debugger_DeviceInfo_ + → fopen(debug_outputs.txt, "w+") + | + v +rrdJsonParser.checkIssueNodeInfo() [after exec] + → tarName = "Device.DeviceInfo" + "_ab1234" + = "Device.DeviceInfo_ab1234" + → uploadDebugoutput(outdir, "Device.DeviceInfo_ab1234") + | + v +rrd_logproc.rrd_logproc_convert_issue_type("Device.DeviceInfo_ab1234") + → "DEVICE_DEVICEINFO_AB1234" (hyphens also preserved) + | + v +rrd_upload / rrd_archive + → Archive: AABBCCDDEEFF_DEVICE_DEVICEINFO_AB1234.tar.gz + → Upload to remote server +``` + +### 4.2 Nominal Flow (without suffix) + +Same as above with: +- `split_issue_type()` returns `suffix = ""` +- `tarName = "Device.DeviceInfo"` (no suffix appended) +- Archive: `AABBCCDDEEFF_DEVICE_DEVICEINFO.tar.gz` + +--- + +## 5. Key Algorithms and Data Structures + +### 5.1 `data_buf` Structure (updated) + +```c +typedef struct mbuffer { + message_type_et mtype; + char *mdata; /* IssueType base (no suffix) */ + char *jsonPath; + bool inDynamic; + bool appendMode; + deepsleep_event_et dsEvent; + char *suffix; /* NEW: optional "_", heap-allocated or NULL */ +} data_buf; +``` + +**Lifecycle of `suffix`:** +- Initialised to `NULL` by `RRD_data_buff_init()`. +- Allocated (via `strdup`) inside `processIssueTypeEvent()` when a non-empty suffix is present. +- Freed in all exit paths of `checkIssueNodeInfo()` and `processIssueTypeInInstalledPackage()`. +- Also freed by `RRD_data_buff_deAlloc()` for callers using the generic de-allocation path. + +## 6. Component Interaction and Interfaces + +### 6.1 Public API Changes + +| Symbol | File | Change | +|--------|------|--------| +| `split_issue_type()` | `rrdJsonParser.h / .c` | **New** function | +| `data_buf.suffix` | `rrdCommon.h` | **New** field (`char *`) | +| `RRD_data_buff_init()` | `rrdInterface.c` | Sets `sbuf->suffix = NULL` | +| `RRD_data_buff_deAlloc()` | `rrdInterface.c` | `free(sbuf->suffix)` added | + +### 6.2 Internal Call Chain + +``` +processIssueTypeEvent() + └─ split_issue_type() [rrdJsonParser.c] + └─ processIssueType() [rrdEventProcess.c] + └─ processIssueTypeInStaticProfile() + └─ checkIssueNodeInfo() [rrdJsonParser.c] + └─ executeCommands() / invokeSanityandCommandExec() + └─ uploadDebugoutput(outdir, tarName) + └─ rrd_logproc_convert_issue_type() + └─ rrd_archive_create() + └─ rrd_upload_file() +``` + +--- + + + +## 7. Glossary + +| Term | Definition | +|------|-----------| +| IssueType | RFC parameter string identifying the debug category (e.g., `Device.DeviceInfo`) | +| Suffix | Optional `_` appended to IssueType (e.g., `_ab1234`), used to distinguish archive names | +| base | The IssueType string before the first `_`; used for JSON profile lookup | +| RFC | Remote Feature Control — device configuration parameter infrastructure | +| RBUS | RDK Message Bus — event delivery mechanism on RDK devices | +| RRD | RDK Remote Debugger — this component | +| tarName | The logical name used when creating and uploading the debug archive | +| data_buf | Internal message buffer passed between RRD processing stages | diff --git a/.github/docs/uploadRRDLogs_Flowcharts.md b/docs/uploadRRDLogs_Flowcharts.md similarity index 100% rename from .github/docs/uploadRRDLogs_Flowcharts.md rename to docs/uploadRRDLogs_Flowcharts.md diff --git a/.github/docs/uploadRRDLogs_HLD.md b/docs/uploadRRDLogs_HLD.md similarity index 100% rename from .github/docs/uploadRRDLogs_HLD.md rename to docs/uploadRRDLogs_HLD.md diff --git a/.github/docs/uploadRRDLogs_LLD.md b/docs/uploadRRDLogs_LLD.md similarity index 100% rename from .github/docs/uploadRRDLogs_LLD.md rename to docs/uploadRRDLogs_LLD.md diff --git a/.github/docs/uploadRRDLogs_Requirements.md b/docs/uploadRRDLogs_Requirements.md similarity index 100% rename from .github/docs/uploadRRDLogs_Requirements.md rename to docs/uploadRRDLogs_Requirements.md diff --git a/.github/docs/uploadRRDLogs_SequenceDiagrams.md b/docs/uploadRRDLogs_SequenceDiagrams.md similarity index 100% rename from .github/docs/uploadRRDLogs_SequenceDiagrams.md rename to docs/uploadRRDLogs_SequenceDiagrams.md diff --git a/run_l2.sh b/run_l2.sh index 6ddf7d8ec..b29d2f956 100644 --- a/run_l2.sh +++ b/run_l2.sh @@ -72,6 +72,8 @@ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_s pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_start_control.json test/functional-tests/tests/test_rrd_start_control.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_start_subscribe_and_wait.json test/functional-tests/tests/test_rrd_start_subscribe_and_wait.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_static_profile_report.json test/functional-tests/tests/test_rrd_static_profile_report.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_static_profile_report_with_suffix.json test/functional-tests/tests/test_rrd_static_profile_report_with_suffix.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_static_profile_report_with_suffix_negative.json test/functional-tests/tests/test_rrd_static_profile_report_with_suffix_negative_case.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_corrupted_static_profile_report.json test/functional-tests/tests/test_rrd_corrupted_static_profile_report.py cp remote_debugger.json /etc/rrd/ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_harmfull_static_profile_report.json test/functional-tests/tests/test_rrd_harmful_command_static_report.py @@ -86,3 +88,5 @@ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_c_ cp remote_debugger.json /etc/rrd/ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_profile_data.json test/functional-tests/tests/test_rrd_profile_data.py + + diff --git a/src/rrdCommon.h b/src/rrdCommon.h index b91ff6f7b..8f5544b77 100644 --- a/src/rrdCommon.h +++ b/src/rrdCommon.h @@ -97,6 +97,7 @@ typedef struct mbuffer { bool inDynamic; bool appendMode; deepsleep_event_et dsEvent; + char *suffix; // Holds the suffix split from issue type string, if any } data_buf; /*Structure for Message Header*/ diff --git a/src/rrdEventProcess.c b/src/rrdEventProcess.c index 5164e7832..eb3c882df 100644 --- a/src/rrdEventProcess.c +++ b/src/rrdEventProcess.c @@ -79,7 +79,35 @@ void processIssueTypeEvent(data_buf *rbuf) cmdBuff = (data_buf *)malloc(sizeof(data_buf)); if (cmdBuff) { - dataMsgLen = strlen(cmdMap[index]) + 1; + char base[128] = {0}; + char local_suffix[128] = {0}; + split_issue_type(cmdMap[index], base, sizeof(base), local_suffix, sizeof(local_suffix)); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty issue type after parsing token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + removeSpecialCharacterfromIssueTypeList(base); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty base after sanitization for token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + dataMsgLen = strlen(base) + 1; RRD_data_buff_init(cmdBuff, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); /* Setting Deafult Values*/ cmdBuff->inDynamic = rbuf->inDynamic; if(cmdBuff->inDynamic) @@ -88,9 +116,19 @@ void processIssueTypeEvent(data_buf *rbuf) } cmdBuff->appendMode = rbuf->appendMode; cmdBuff->mdata = (char *)calloc(1, dataMsgLen); + + /* Store suffix for this issue type */ + cmdBuff->suffix = NULL; + if (local_suffix[0] != '\0') { + cmdBuff->suffix = strdup(local_suffix); + if (cmdBuff->suffix == NULL) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to allocate memory for suffix... \n", __FUNCTION__, __LINE__); + } + } if (cmdBuff->mdata) { - strncpy((char *)cmdBuff->mdata, cmdMap[index], dataMsgLen); + strncpy((char *)cmdBuff->mdata, base, dataMsgLen); processIssueType(cmdBuff); } else @@ -99,6 +137,11 @@ void processIssueTypeEvent(data_buf *rbuf) } if(cmdBuff) { + if (cmdBuff->suffix) + { + free(cmdBuff->suffix); + cmdBuff->suffix = NULL; + } free(cmdBuff); cmdBuff = NULL; } @@ -392,7 +435,10 @@ static void processIssueTypeInStaticProfile(data_buf *rbuf, issueNodeData *pIssu #if !defined(GTEST_ENABLE) jsonParsed = readAndParseJSON(RRD_JSON_FILE); #else - jsonParsed = readAndParseJSON(rbuf->jsonPath); + if (rbuf->jsonPath != NULL) + { + jsonParsed = readAndParseJSON(rbuf->jsonPath); + } #endif if (jsonParsed == NULL) { // Static Profile JSON Parsing or Read Fail @@ -555,6 +601,29 @@ static void processIssueTypeInInstalledPackage(data_buf *rbuf, issueNodeData *pI suffixlen = strlen(RDM_PKG_SUFFIX); dynJSONPath = (char *)malloc(persistentAppslen + prefixlen + suffixlen + strlen(pIssueNode->Node) + rrdjsonlen + 1); #else + if ((rbuf == NULL) || (rbuf->jsonPath == NULL)) + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: jsonPath is NULL, skipping installed package check... \n", __FUNCTION__, __LINE__); + if (rbuf != NULL) + { + if (rbuf->mdata != NULL) + { + free(rbuf->mdata); + rbuf->mdata = NULL; + } + if (rbuf->suffix != NULL) + { + free(rbuf->suffix); + rbuf->suffix = NULL; + } + if (rbuf->jsonPath != NULL) + { + free(rbuf->jsonPath); + rbuf->jsonPath = NULL; + } + } + return; + } int utjsonlen = strlen(rbuf->jsonPath); dynJSONPath = (char *)malloc(utjsonlen + 1); #endif @@ -639,7 +708,7 @@ static void removeSpecialCharacterfromIssueTypeList(char *str) while (str[source] != '\0') { - if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') + if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') { str[destination] = str[source]; ++destination; @@ -663,7 +732,6 @@ static int issueTypeSplitter(char *input_str, const char delimeter, char ***args int cnt = 1, i = 0; char *str = input_str; - removeSpecialCharacterfromIssueTypeList(str); while (*str == delimeter) str++; diff --git a/src/rrdInterface.c b/src/rrdInterface.c index b69dd8936..30291cd84 100644 --- a/src/rrdInterface.c +++ b/src/rrdInterface.c @@ -275,6 +275,7 @@ void RRD_data_buff_init(data_buf *sbuf, message_type_et sndtype, deepsleep_event sbuf->inDynamic = false; sbuf->appendMode = false; sbuf->dsEvent = deepSleepEvent; + sbuf->suffix = NULL; } /*Function: RRD_data_buff_deAlloc @@ -295,6 +296,10 @@ void RRD_data_buff_deAlloc(data_buf *sbuf) { free(sbuf->jsonPath); } + if (sbuf->suffix) + { + free(sbuf->suffix); + } free(sbuf); } } diff --git a/src/rrdJsonParser.c b/src/rrdJsonParser.c index e06d93ac2..2fce93676 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -24,6 +24,8 @@ #include #include #include +/* Maximum suffix length, including the leading '_' character. */ +#define RRD_MAX_SUFFIX_LEN 9 /* * @function removeSpecialChar @@ -46,6 +48,102 @@ void removeSpecialChar(char *str) } } + +/* + * @function split_issue_type + * @brief Utility to split base and suffix from issue type string. + * The input is always split at the first '_'. The base is the part + * before the underscore (never contains '_'). The suffix is only + * kept when its total length (including the leading '_') is at most + * RRD_MAX_SUFFIX_LEN (9) characters; longer suffixes are discarded. + * After length validation the suffix is sanitized: only the characters + * [A-Za-z0-9_-] are retained so that the value is safe to use in file + * names and shell command arguments without risk of injection. + * If no underscore is present the full input is the base. + * Examples: + * "Device.DeviceTime_ab12345" → base="Device.DeviceTime", + * suffix="_ab12345" (8 chars, accepted) + * "Device.DeviceTime_Search-uuid-very-long" + * → base="Device.DeviceTime", + * suffix="" (>9 chars, discarded) + * "Device.DeviceTime" → base="Device.DeviceTime", + * suffix="" + * "Device.DeviceTime_ab;rm" → base="Device.DeviceTime", + * suffix="_abrm" (unsafe ';' stripped) + * @param const char *input - The input string to split. + * @param char *base - Buffer to store the base part (never contains '_'). + * @param size_t base_len - Size of the base buffer. + * @param char *suffix - Buffer to store the suffix part when valid, else "". + * @param size_t suffix_len - Size of the suffix buffer. + * @return void + */ + +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len) +{ + if (base && base_len > 0) + { + base[0] = '\0'; + } + if (suffix && suffix_len > 0) + { + suffix[0] = '\0'; + } + + if (!input || !base || !suffix) + { + return; + } + + if (base_len == 0 || suffix_len == 0) + { + return; + } + + const char *underscore = strchr(input, '_'); + if (underscore) + { + /* Always split at the first underscore — base never contains '_' */ + size_t b_len = (size_t)(underscore - input); + if (b_len >= base_len) b_len = base_len - 1; + strncpy(base, input, b_len); + base[b_len] = '\0'; + + /* Keep the suffix only when its total length (including '_') is + * within the allowed limit; longer tokens are discarded. */ + if (strlen(underscore) <= RRD_MAX_SUFFIX_LEN) + { + /* Sanitize: retain only [A-Za-z0-9_-] to prevent injection when + * the suffix is later embedded in file names or command arguments. */ + size_t si = 0, di = 0; + size_t max_copy = suffix_len - 1; + while (underscore[si] != '\0' && di < max_copy) + { + char ch = underscore[si]; + if (isalnum((unsigned char)ch) || ch == '_' || ch == '-') + { + suffix[di++] = ch; + } + si++; + } + suffix[di] = '\0'; + } + else + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Suffix after '%s' exceeds max length (%zu > %d); discarding\n", + __FUNCTION__, __LINE__, base, strlen(underscore), RRD_MAX_SUFFIX_LEN); + suffix[0] = '\0'; + } + } + else + { + /* No underscore — full input is the base */ + strncpy(base, input, base_len - 1); + base[base_len - 1] = '\0'; + suffix[0] = '\0'; + } +} + + /* * @function getParamcount * @brief Calculates the total number of nodes (elements) in the input string, excluding delimiters. @@ -515,7 +613,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Memory allocation failed for rfcbuf\n",__FUNCTION__,__LINE__); free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } @@ -535,7 +637,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: %s Directory creation failed!!!\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } else @@ -576,7 +682,26 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf else { RDK_LOG(RDK_LOG_DEBUG,LOG_REMDEBUG,"[%s:%d]: Continue uploading Debug Report for %s from %s... \n",__FUNCTION__,__LINE__,buff->mdata,outdir); - status = uploadDebugoutput(outdir,buff->mdata); + // Use the persisted suffix from buff for upload + char tarName[512] = {0}; + int tar_name_len = 0; + if (buff->suffix && buff->suffix[0] != '\0') + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s%s", buff->mdata, buff->suffix); + } + else + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s", buff->mdata); + } + if ((tar_name_len < 0) || ((size_t)tar_name_len >= sizeof(tarName))) + { + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Failed to build upload file name for %s. snprintf result:%d, buffer size:%zu\n", __FUNCTION__,__LINE__,buff->mdata,tar_name_len,sizeof(tarName)); + status = -1; + } + else + { + status = uploadDebugoutput(outdir, tarName); + } if(status != 0) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: RRD Upload Script Execution Failed!!! status:%d\n",__FUNCTION__,__LINE__,status); @@ -588,14 +713,22 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf } free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } else { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: No Command excuted as RRD Failed to change directory:%s\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } } } diff --git a/src/rrdJsonParser.h b/src/rrdJsonParser.h index 299436101..87c4a2622 100644 --- a/src/rrdJsonParser.h +++ b/src/rrdJsonParser.h @@ -47,6 +47,8 @@ issueData* getIssueCommandInfo(issueNodeData *issuestructNode, cJSON *jsoncfg,ch bool processAllDebugCommand(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); bool processAllDeepSleepAwkMetricsCommands(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len); + #ifdef __cplusplus } #endif diff --git a/src/rrdRunCmdThread.c b/src/rrdRunCmdThread.c index ce873d5d5..650144cdb 100644 --- a/src/rrdRunCmdThread.c +++ b/src/rrdRunCmdThread.c @@ -287,7 +287,7 @@ bool executeCommands(issueData *cmdinfo) char pathname[BUF_LEN_256] = {'\0'}; char *outdirpath = NULL; char finalOutFile[BUF_LEN_256] = {'\0'}; - char remoteDebuggerServiceStr[BUF_LEN_256] = {'\0'}; + char remoteDebuggerServiceStr[BUF_LEN_512] = {'\0'}; char *printbuffer = NULL; FILE *filePointer; const char *remoteDebuggerPrefix = "remote_debugger_"; @@ -357,7 +357,7 @@ bool executeCommands(issueData *cmdinfo) strncat(finalOutFile,RRD_OUTPUT_FILE, strlen(RRD_OUTPUT_FILE) + 1); /* Open debug_output.txt file*/ - filePointer = fopen(finalOutFile, "a+"); + filePointer = fopen(finalOutFile, "w+"); if (filePointer == NULL) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Unable to Open File:%s\n",__FUNCTION__,__LINE__,finalOutFile); @@ -376,21 +376,18 @@ bool executeCommands(issueData *cmdinfo) /*Executing Commands using systemd-run*/ RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Executing following commands using systemd-run:\n \"%s\"\n",__FUNCTION__,__LINE__,cmdData->command); - - strncpy(remoteDebuggerServiceStr, remoteDebuggerPrefix, sizeof(remoteDebuggerServiceStr) - 1); - remoteDebuggerServiceStr[sizeof(remoteDebuggerServiceStr) - 1] = '\0'; - strncat(remoteDebuggerServiceStr, cmdData->rfcvalue, sizeof(remoteDebuggerServiceStr) - strlen(remoteDebuggerServiceStr) - 1); - + time_t epochTime = time(NULL); + snprintf(remoteDebuggerServiceStr, sizeof(remoteDebuggerServiceStr),"%s%s%ld", remoteDebuggerPrefix, cmdData->rfcvalue, (long)epochTime); removeQuotes(cmdData->command); FILE *systemdfp = v_secure_popen("r", "systemd-run -r --unit=%s --service-type=oneshot -p RemainAfterExit=yes /bin/sh -c %s", remoteDebuggerServiceStr, cmdData->command); if(systemdfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(systemdfp, filePointer); v_secure_pclose(systemdfp); } @@ -400,11 +397,11 @@ bool executeCommands(issueData *cmdinfo) FILE *journalctlfp = v_secure_popen("r", "journalctl -a -u %s", remoteDebuggerServiceStr); if(journalctlfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(journalctlfp, filePointer); v_secure_pclose(journalctlfp); } @@ -417,7 +414,7 @@ bool executeCommands(issueData *cmdinfo) sleep(cmdData->timeout); /*Stop or Reset runtime service for issue*/ - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping remote_debugger_%s service...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping %s service...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); #if !defined(GTEST_ENABLE) v_secure_system("systemctl stop %s", remoteDebuggerServiceStr); free(cmdData->rfcvalue); // free rfcvalue received from RRDEventThreadFunc diff --git a/src/rrdRunCmdThread.h b/src/rrdRunCmdThread.h index a1bd9816a..fb92fb087 100644 --- a/src/rrdRunCmdThread.h +++ b/src/rrdRunCmdThread.h @@ -48,6 +48,7 @@ extern "C" #endif #define RRD_OUTPUT_FILE "debug_outputs.txt" #define BUF_LEN_256 256 +#define BUF_LEN_512 512 /*Public Function*/ void initCache(void); diff --git a/src/rrd_logproc.c b/src/rrd_logproc.c index 30c0cff1c..a33aee466 100644 --- a/src/rrd_logproc.c +++ b/src/rrd_logproc.c @@ -121,7 +121,8 @@ int rrd_logproc_convert_issue_type(const char *input, char *output, size_t size) for (size_t i = 0; input[i] && j < size-1; ++i) { char c = input[i]; if (isalnum((unsigned char)c)) output[j++] = toupper((unsigned char)c); - else if (c == '_' || c == '-' || c == '.') output[j++] = '_'; + else if (c == '_' || c == '.') output[j++] = '_'; + else if (c == '-') output[j++] = '-'; // preserve hyphens so suffix UUID tokens remain distinct // skip other chars } output[j] = 0; diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 7b8c6c837..059e5ce41 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1799,11 +1799,13 @@ TEST_F(RemoveItemTest, HandlesCacheNotNullAndCacheNotEqualsRrdCachecnode) node->mdata = strdup("PkgData"); node->issueString = strdup("IssueString"); node->next = NULL; + node->prev = NULL; cacheDataNode = node; cacheData *node_dummy = (cacheData *)malloc(sizeof(cacheData)); node_dummy->mdata = strdup("PkgData"); node_dummy->issueString = strdup("IssueString"); node_dummy->next = NULL; + node_dummy->prev = NULL; remove_item(node_dummy); EXPECT_NE(cacheDataNode, nullptr); @@ -1863,15 +1865,19 @@ TEST(RemoveSpecialCharacterfromIssueTypeListTest, HandlesStringWithConsecutiveSp /* --------------- Test issueTypeSplitter() from rrdEventProcess --------------- */ TEST(IssueTypeSplitterTest, HandlesStringWithSpecialCharacters) { + /* issueTypeSplitter now performs pure token splitting only; special-character + * removal is done separately by the caller (processIssueTypeEvent) on the + * extracted base, so raw tokens including special chars are returned here. */ char str[] = "a@,b,&,cd,ef"; char **args = NULL; int count = issueTypeSplitter(str, ',', &args); - ASSERT_EQ(count, 4); - ASSERT_STREQ(args[0], "a"); + ASSERT_EQ(count, 5); + ASSERT_STREQ(args[0], "a@"); ASSERT_STREQ(args[1], "b"); - ASSERT_STREQ(args[2], "cd"); - ASSERT_STREQ(args[3], "ef"); + ASSERT_STREQ(args[2], "&"); + ASSERT_STREQ(args[3], "cd"); + ASSERT_STREQ(args[4], "ef"); for (int i = 0; i < count; i++) { @@ -1907,6 +1913,237 @@ TEST(IssueTypeSplitterTest, HandlesEmptyString) free(args); } +/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ +TEST(SplitIssueTypeTest, NoUnderscoreReturnsFull) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, UnderscoreSplitsBaseAndSuffix) +{ + /* Short suffix (total length including '_' is ≤ 9) is accepted and preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab12345", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab12345"); +} + +TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) +{ + /* "abc_def_ghi": suffix "_def_ghi" is 8 chars (≤ 9) → accepted and preserved. + * Only the first '_' is used as the split point; base never contains '_'. */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_def_ghi", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_def_ghi"); +} + +TEST(SplitIssueTypeTest, EmptyInputProducesEmptyOutputs) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullInputDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* Should return without crashing and clear provided outputs to empty strings */ + split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); + /* NULL input clears the output buffers when buffer pointers are provided */ + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, BaseTruncatedWhenTooSmall) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9) → accepted. + * Base = "abc" (before '_'); with a 4-byte buffer this fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) +{ + /* "abc_12345678": suffix "_12345678" is 9 chars (≤ 9, accepted). Suffix buffer + * is only 5 bytes so suffix is truncated to "_123" + NUL. */ + char base[64] = {0}; + char suffix[5] = {0}; + split_issue_type("abc_12345678", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_123"); + EXPECT_EQ(suffix[sizeof(suffix) - 1], '\0'); + EXPECT_EQ(strlen(suffix), (size_t)(sizeof(suffix) - 1)); +} + +TEST(SplitIssueTypeTest, LeadingUnderscoreGivesEmptyBase) +{ + /* "_suffixonly": split at '_' gives empty base; suffix "_suffixonly" is 11 chars + * (> 9) so it is discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_suffixonly", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullBaseDoesNotCrash) +{ + char suffix[64] = {0}; + /* NULL base pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); + /* suffix remains unchanged when base is NULL */ + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullSuffixDoesNotCrash) +{ + char base[64] = {0}; + /* NULL suffix pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); + /* base remains unchanged when suffix is NULL */ + EXPECT_STREQ(base, ""); +} + +TEST(SplitIssueTypeTest, ZeroBaseLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* base_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, 0, suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ZeroSuffixLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* suffix_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, sizeof(base), suffix, 0); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ExactFitBase) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9, accepted). + * Base = "abc" (before '_'); 4-byte buffer fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_EQ(base[3], '\0'); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, OnlyUnderscoreInput) +{ + /* "_": split at '_' gives empty base; suffix is "_" (1 char, ≤ 9 → accepted) */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, NineCharSuffixIsAccepted) +{ + /* Suffix of exactly 9 chars (the upper boundary, inclusive) must be accepted */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_12345678", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_12345678"); +} + +TEST(SplitIssueTypeTest, LongSuffixIsDiscarded) +{ + /* Suffix longer than 9 chars is discarded regardless of its content */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceInfo_1234567890", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceInfo"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixExceedingMaxLengthDiscarded) +{ + /* "_Random-token" is 13 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_Random-token", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixSeventeenCharsDiscarded) +{ + /* "_Search_something" is 17 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_Search_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixTwentyCharsDiscarded) +{ + /* "_LogSearch_something" is 20 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_LogSearch_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixWithUnsafeCharsIsSanitized) +{ + /* "_ab;rm" is 6 chars (≤ 9, accepted) but ';' is unsafe and must be stripped. + * Expected sanitized suffix: "_abrm" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab;rm", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_abrm"); +} + +TEST(SplitIssueTypeTest, SuffixWithOnlyUnsafeCharsBecomesUnderscore) +{ + /* "_!@#" is 4 chars (≤ 9, accepted length-wise) but all payload chars are unsafe. + * After sanitization only the leading '_' remains → suffix="_" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_!@#", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, SuffixHyphensPreserved) +{ + /* "_ab-cd" is 6 chars (≤ 9, accepted) and '-' is in the safe set → preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab-cd", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab-cd"); +} + /* --------------- Test processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -1989,11 +2226,93 @@ TEST(ProcessIssueTypeEvntTest, RBufIsNull){ } TEST(ProcessIssueTypeEvntTest, inDynamic_NoJson){ - data_buf rbuf; + data_buf rbuf = {}; rbuf.mdata = strdup("a"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceTime" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceInfo" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) +{ + /* "_Random-token" is 13 chars (> 9): suffix discarded; base = "Device.DeviceTime" */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Random-token"); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ + /* Comma-separated list: one plain type, one with long suffix (> 9, discarded), + * one with another long suffix (> 9, discarded) */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime,Device.DeviceInfo_Search-1234,Device.Net_BadSuffix"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; all entries are processed without leaks */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, WhitespaceOnlyIssueTypeIsSkipped) +{ + /* When the IssueType value from RBUS is whitespace (e.g. a space), + * removeSpecialCharacterfromIssueTypeList() strips it to an empty string. + * processIssueTypeEvent() must detect the post-sanitization empty base + * and skip processing without crashing or invoking processIssueType. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(" "); /* single space — all-special after split */ + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Must not crash and must not reach getIssueInfo with an empty mdata */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, EmptyStringIssueTypeIsSkipped) +{ + /* An empty-string mdata must be handled gracefully — issueTypeSplitter + * returns 1 token that is an empty string, which split_issue_type then + * maps to an empty base, causing the entry to be skipped. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(""); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; } /* ======================== rrdExecuteScript ==============*/ @@ -2117,12 +2436,22 @@ TEST(RRDDataBuffInitTest, InitializeDataBuff) EXPECT_EQ(sbuf.dsEvent, deepSleepEvent); } +TEST(RRDDataBuffInitTest, SuffixInitializedToNull) +{ + /* Verify that the newly added suffix field is initialised to NULL */ + data_buf sbuf; + sbuf.suffix = reinterpret_cast(0xDEADBEEF); /* pre-fill with garbage */ + RRD_data_buff_init(&sbuf, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); + EXPECT_EQ(sbuf.suffix, nullptr); +} + /* --------------- Test RRD_data_buff_deAlloc() from rrdIarm --------------- */ TEST(RRDDataBuffDeAllocTest, DeallocateDataBuff) { data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); sbuf->mdata = (char *)malloc(10 * sizeof(char)); sbuf->jsonPath = (char *)malloc(10 * sizeof(char)); + sbuf->suffix = nullptr; ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } @@ -2134,6 +2463,28 @@ TEST(RRDDataBuffDeAllocTest, NullPointer) ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } +TEST(RRDDataBuffDeAllocTest, DeallocateWithSuffixSet) +{ + /* Verify that suffix is freed without crash when it is non-NULL */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = strdup("IssueType"); + sbuf->jsonPath = nullptr; + sbuf->suffix = strdup("_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + +TEST(RRDDataBuffDeAllocTest, DeallocateWithAllFieldsNull) +{ + /* All pointer fields NULL: should not crash */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = nullptr; + sbuf->jsonPath = nullptr; + sbuf->suffix = nullptr; + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + /* --------------- Test RRD_unsubscribe() from rrdIarm --------------- */ class RRDUnsubscribeTest : public ::testing::Test @@ -3684,6 +4035,7 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; + rbuf.suffix = strdup("_ab12345"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4444,12 +4796,22 @@ TEST_F(RRDUploadOrchestrationTest, SpecialCharactersInIssueType) { char sanitized[64]; int result = rrd_logproc_convert_issue_type("test-issue.sub@special!", sanitized, sizeof(sanitized)); EXPECT_EQ(result, 0); - // Should only contain alphanumeric and underscore + // Should only contain alphanumeric, underscore, and hyphen for (const char *p = sanitized; *p; ++p) { - EXPECT_TRUE(isalnum(*p) || *p == '_'); + EXPECT_TRUE(isalnum(*p) || *p == '_' || *p == '-'); } } +// Suffix with hyphens: hyphens must be preserved so portal can parse filename +TEST_F(RRDUploadOrchestrationTest, IssueTypeWithSuffixHyphensPreserved) { + char sanitized[128]; + // Simulates issue type after normalizeIssueName: dots→underscore, hyphens kept + int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); + EXPECT_EQ(result, 0); + // Hyphens in suffix must survive so the archive filename delimiter structure is intact + EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); +} + // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files diff --git a/test/functional-tests/L2_Test_Coverage.md b/test/functional-tests/L2_Test_Coverage.md new file mode 100644 index 000000000..fcd872f15 --- /dev/null +++ b/test/functional-tests/L2_Test_Coverage.md @@ -0,0 +1,399 @@ +# Remote Debugger L2 Test Coverage Report + +**Generated:** 2026-05-19 +**Component:** `remotedebugger` (src/) +**Test Suite:** `test/functional-tests/` + +--- + +## Executive Summary + +| Metric | Count | +|---|:---:| +| Feature files | 21 | +| Feature scenarios | 90 | +| Test files (pytest) | 22 | +| Test functions (`test_*`) | 112 | +| Feature→Test mapped pairs | 21 / 21 (+ 1 orphan test) | +| Source modules (always compiled) | 9 | +| Source modules (conditional IARMBUS) | 7 | +| **Overall feature coverage** | **95%** (21/22 test files have matching features) | +| **Source behavior coverage** | **~65%** (happy paths covered; error/edge paths mostly untested) | + +--- + +## 1. Feature File ↔ Test File Mapping + +### Fully Mapped (feature + test exist) + +| # | Feature File | Scenarios | Test File | Test Functions | Status | +|:---:|---|:---:|---|:---:|:---:| +| 1 | `rrd_start_subscribe_and_wait.feature` | 1 | `test_rrd_start_subscribe_and_wait.py` | 4 | PASS | +| 2 | `rrd_start_control.feature` | 2 | `test_rrd_start_control.py` | 1 | PASS | +| 3 | `rrd_single_instance.feature` | 1 | `test_rrd_single_instance.py` | 3 | PASS | +| 4 | `rrd_static_profile_report.feature` | 5 | `test_rrd_static_profile_report.py` | 5 | PASS | +| 5 | `rrd_static_profile_category_report.feature` | 5 | `test_rrd_static_profile_category_report.py` | 5 | PASS | +| 6 | `test_rrd_static_profile_report_with_suffix.feature` | 4 | `test_rrd_static_profile_report_with_suffix.py` | 5 | PASS | +| 7 | `test_rrd_static_profile_report_with_suffix_negative_case.feature` | 4 | `test_rrd_static_profile_report_with_suffix_negative_case.py` | 5 | PASS | +| 8 | `rrd_background_cmd_static_profile_report.feature` | 5 | `test_rrd_background_cmd_static_profile_report.py` | 5 | PASS | +| 9 | `rrd_dynamic_profile_report.feature` | 5 | `test_rrd_dynamic_profile_report.py` | 9 | PASS | +| 10 | `rrd_dynamic_profile_subcategory_report.feature` | 5 | `test_rrd_dynamic_subcategory_report.py` | 7 | PASS | +| 11 | `rrd_dynamic_profile_missing_report.feature` | 4 | `test_rrd_dynamic_profile_missing_report.py` | 7 | PASS | +| 12 | `rrd_append_report.feature` | 4 | `test_rrd_append_report.py` | 7 | PASS | +| 13 | `rrd_append_dynamic_profile_static_not_found.feature` | 4 | `test_rrd_append_dynamic_profile_static_notfound.py` | 7 | PASS | +| 14 | `rrd_harmful_command_static_report.feature` | 5 | `test_rrd_harmful_command_static_report.py` | 5 | PASS | +| 15 | `test_rrd_dynamic_profile_harmful_report.feature` | 5 | `test_rrd_dynamic_profile_harmful_report.py` | 7 | PASS | +| 16 | `rrd_corrupted_static_profile_report.feature` | 4 | `test_rrd_corrupted_static_profile_report.py` | 4 | PASS | +| 17 | `rrd_static_profile_missing_command_report.feature` | 5 | `test_rrd_static_profile_missing_command_report.py` | 5 | PASS | +| 18 | `rrd_empty_issuetype_event.feature` | 2 | `test_rrd_empty_issuetype_event.py` | 2 | PASS | +| 19 | `rrd_deepsleep_static_report.feature` | 2 | `test_rrd_deepsleep_static_report.py` | 5 | PASS | +| 20 | `rrd_debug_report_upload.feature` | 6 | `test_rrd_debug_report_upload.py` | 5 | PASS | +| 21 | `rrd_c_api_upload.feature` | 21 | `test_rrd_c_api_upload.py` | 5 | GAP | +| | **Totals** | **97** | | **112** | | + +> **Note:** Scenario count for `rrd_c_api_upload.feature` (21) far exceeds its test function count (5). See Section 3 for details. + +### Orphan Tests (test exists, no matching feature file) + +| Test File | Test Functions | Description | Gap | +|---|:---:|---|---| +| `test_rrd_profile_data.py` | 3 | RBUS profile data SET/GET via `rbuscli` | **Missing `.feature` file** | + +### Orphan Features (feature exists, no matching test file) + +None — all 21 feature files have corresponding test files. + +--- + +## 2. Per-Behavior Coverage Detail + +### 2.1 Daemon Lifecycle + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| RBUS subscription + event wait | `rrd_start_subscribe_and_wait` | `test_rrd_start_subscribe_and_wait` | YES | +| RFC enable → daemon starts | `rrd_start_control` | `test_rrd_start_control` | YES | +| RFC disable → daemon stops | `rrd_start_control` | `test_rrd_start_control` | YES | +| Single instance enforcement | `rrd_single_instance` | `test_rrd_single_instance` | YES | +| Message queue creation failure | — | — | **NO** | +| Event thread creation failure | — | — | **NO** | +| Signal handling / graceful shutdown | — | — | **NO** | +| Device info file read failure | — | — | **NO** | + +### 2.2 Static Profile Processing + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| Config file exists check | `rrd_static_profile_report` | `test_rrd_static_profile_report` | YES | +| Output directory exists check | `rrd_static_profile_report` | `test_rrd_static_profile_report` | YES | +| IssueType event trigger + message flow | `rrd_static_profile_report` | `test_rrd_static_profile_report` | YES | +| JSON parse success + command execution | `rrd_static_profile_report` | `test_rrd_static_profile_report` | YES | +| Upload report success/failure | `rrd_static_profile_report` | `test_rrd_static_profile_report` | YES | +| Category-only issue type (all sub-nodes) | `rrd_static_profile_category_report` | `test_rrd_static_profile_category_report` | YES | +| Suffixed issue type | `test_rrd_static_profile_report_with_suffix` | `test_rrd_static_profile_report_with_suffix` | YES | +| Overlength suffix (negative) | `test_rrd_static_profile_report_with_suffix_negative_case` | `test_rrd_static_profile_report_with_suffix_negative_case` | YES | +| Background command execution | `rrd_background_cmd_static_profile_report` | `test_rrd_background_cmd_static_profile_report` | YES | +| Missing command in profile | `rrd_static_profile_missing_command_report` | `test_rrd_static_profile_missing_command_report` | YES | +| Corrupted/invalid JSON profile | `rrd_corrupted_static_profile_report` | `test_rrd_corrupted_static_profile_report` | YES | + +### 2.3 Dynamic Profile Processing + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| Dynamic profile fallback (static miss) | `rrd_dynamic_profile_report` | `test_rrd_dynamic_profile_report` | YES | +| Dynamic subcategory | `rrd_dynamic_profile_subcategory_report` | `test_rrd_dynamic_subcategory_report` | YES | +| Dynamic profile missing → RDM trigger | `rrd_dynamic_profile_missing_report` | `test_rrd_dynamic_profile_missing_report` | YES | +| Append mode (static + dynamic) | `rrd_append_report` | `test_rrd_append_report` | YES | +| Append when static not found | `rrd_append_dynamic_profile_static_not_found` | `test_rrd_append_dynamic_profile_static_notfound` | YES | +| RDM download event (cache miss) | — | — | **NO** | +| Dynamic profile JSON parse failure | — | — | **NO** | + +### 2.4 Harmful Command Detection + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| Static profile harmful command abort | `rrd_harmful_command_static_report` | `test_rrd_harmful_command_static_report` | YES | +| Dynamic profile harmful command abort | `test_rrd_dynamic_profile_harmful_report` | `test_rrd_dynamic_profile_harmful_report` | YES | +| Macro replacement edge cases | — | — | **NO** | +| Background command modification | — | — | **PARTIAL** (via background cmd test) | + +### 2.5 Event Handling + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| IssueType RBUS event | Multiple features | Multiple tests | YES | +| Empty IssueType event | `rrd_empty_issuetype_event` | `test_rrd_empty_issuetype_event` | YES | +| Deep sleep event | `rrd_deepsleep_static_report` | `test_rrd_deepsleep_static_report` | YES | +| WebCfg event (MsgPack decode) | — | — | **NO** | +| WebCfg corrupted data | — | — | **NO** | +| Multiple simultaneous IssueType events | — | — | **NO** | +| Invalid deep sleep event type | — | — | **NO** | + +### 2.6 Upload & Archive + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| Upload via shell script | `rrd_debug_report_upload` | `test_rrd_debug_report_upload` | YES | +| Upload + download validation | `rrd_debug_report_upload` | `test_rrd_debug_report_upload` | YES | +| C API `rrd_upload_orchestrate` (happy path) | `rrd_c_api_upload` | `test_rrd_c_api_upload` | YES | +| C API NULL parameters | `rrd_c_api_upload` | — | **GAP** | +| C API empty directory | `rrd_c_api_upload` | — | **GAP** | +| C API non-existent directory | `rrd_c_api_upload` | — | **GAP** | +| C API config loading validation | `rrd_c_api_upload` | — | **GAP** | +| C API MAC retrieval | `rrd_c_api_upload` | — | **GAP** | +| C API timestamp generation | `rrd_c_api_upload` | — | **GAP** | +| C API issue type sanitization | `rrd_c_api_upload` | — | **GAP** | +| C API archive creation | `rrd_c_api_upload` | — | **GAP** | +| C API upload execution | `rrd_c_api_upload` | — | **GAP** | +| C API cleanup after success/failure | `rrd_c_api_upload` | — | **GAP** | +| C API concurrent upload lock | `rrd_c_api_upload` | — | **GAP** | +| C API LOGUPLOAD_ENABLE | `rrd_c_api_upload` | — | **GAP** | +| C API end-to-end with RFC trigger | `rrd_c_api_upload` | — | **GAP** | +| C API error propagation | `rrd_c_api_upload` | — | **GAP** | +| Upload lock file contention | — | — | **NO** | +| Archive CPU throttle logic | — | — | **NO** | + +### 2.7 Profile Data SET/GET (RBUS) + +| Behavior | Feature | Test | Covered | +|---|---|---|:---:| +| `setProfileData` / `getProfileData` | — | `test_rrd_profile_data` | **PARTIAL** (no feature) | +| Profile category load error | — | — | **NO** | +| Profile file write error | — | — | **NO** | + +--- + +## 3. Feature ↔ Test Gap Analysis + +### 3.1 `rrd_c_api_upload.feature` — Major Scenario-to-Test Gap + +The feature file documents **21 scenarios** covering the full `rrd_upload_orchestrate` C API, but the test file `test_rrd_c_api_upload.py` only implements **5 test functions** that cover the basic end-to-end flow (config check, dir check, start, trigger event, upload report). + +**Missing test implementations for feature scenarios:** + +| Feature Scenario | Test Status | +|---|---| +| Validate rrd_upload_orchestrate C API with valid parameters | Covered (within `test_remote_debugger_trigger_event`) | +| Test rrd_upload_orchestrate with NULL upload directory | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate with NULL issue type | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate with empty upload directory | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate with non-existent directory | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate configuration loading | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate MAC address retrieval | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate timestamp generation | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate issue type sanitization | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate archive creation | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate upload execution | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate cleanup after success | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate cleanup after upload failure | **NOT IMPLEMENTED** | +| Test uploadDebugoutput wrapper function | **NOT IMPLEMENTED** | +| Test concurrent upload lock handling | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate with LOGUPLOAD_ENABLE issue type | **NOT IMPLEMENTED** | +| Test remote debugger end-to-end with RFC trigger | Covered | +| Test upload report validation with success path | Covered (within `test_remotedebugger_upload_report`) | +| Test upload report validation with failure path | Covered (within `test_remotedebugger_upload_report`) | +| Test upload report with legacy log compatibility | **NOT IMPLEMENTED** | +| Test rrd_upload_orchestrate error propagation | **NOT IMPLEMENTED** | + +**Gap: 16 of 21 scenarios have no dedicated test implementation.** + +### 3.2 `test_rrd_profile_data.py` — Orphan Test (No Feature File) + +This test file contains 3 test functions exercising `rbuscli set/get` on profile data RBUS elements (`setProfileData` / `getProfileData`). No corresponding `.feature` file exists. + +**Recommendation:** Create `rrd_profile_data.feature` to document this behavior. + +### 3.3 Scenario Count vs Test Count Discrepancy + +Some test files implement more test functions than their feature has scenarios, due to setup/teardown and prerequisite tests being split more granularly: + +| Feature File | Scenarios | Test File | Tests | Delta | +|---|:---:|---|:---:|:---:| +| `rrd_start_subscribe_and_wait` | 1 | `test_rrd_start_subscribe_and_wait` | 4 | +3 | +| `rrd_single_instance` | 1 | `test_rrd_single_instance` | 3 | +2 | +| `rrd_dynamic_profile_report` | 5 | `test_rrd_dynamic_profile_report` | 9 | +4 | +| `rrd_dynamic_profile_missing_report` | 4 | `test_rrd_dynamic_profile_missing_report` | 7 | +3 | +| `rrd_append_report` | 4 | `test_rrd_append_report` | 7 | +3 | +| `rrd_append_dynamic_profile_static_not_found` | 4 | `test_rrd_append_dynamic_profile_static_notfound` | 7 | +3 | +| `rrd_dynamic_profile_harmful_report` | 5 | `test_rrd_dynamic_profile_harmful_report` | 7 | +2 | +| `rrd_deepsleep_static_report` | 2 | `test_rrd_deepsleep_static_report` | 5 | +3 | +| `rrd_c_api_upload` | 21 | `test_rrd_c_api_upload` | 5 | **-16** | + +The **only deficit** is `rrd_c_api_upload` where the feature documents far more scenarios than tests implement. + +--- + +## 4. Source Module Coverage Analysis + +### 4.1 Module-Level Coverage Summary + +| Source Module | Happy Path | Error Paths | L2 Coverage | +|---|:---:|:---:|---| +| `rrdMain.c` | YES | NO | Startup, RFC check, event thread — tested. Msgqueue/thread failures — not tested. | +| `rrdInterface.c` | YES | NO | RBUS registration, event handlers — tested. Registration failures, file I/O errors — not tested. | +| `rrdEventProcess.c` | YES | PARTIAL | Static/dynamic/append/deepsleep — tested. WebCfg event, malloc failures — not tested. | +| `rrdJsonParser.c` | YES | PARTIAL | Valid/corrupted/missing JSON — tested. Dir creation failures, malloc failures — not tested. | +| `rrdRunCmdThread.c` | YES | NO | Command execution, output files — tested. File write errors, systemd-run failures — not tested. | +| `rrdCommandSanity.c` | YES | NO | Harmful command detection — tested. Macro replacement errors — not tested. | +| `rrdDynamic.c` | YES | NO | Dynamic profile, deep sleep — tested. RBUS set failure, invalid event type — not tested. | +| `rrdExecuteScript.c` | YES | NO | Upload orchestration — tested. Script exec failure, API failure — not tested. | +| `rrdMsgPackDecoder.c` | NO | NO | **Entirely untested** — WebCfg MsgPack decode not exercised by any L2 test. | +| `rrd_config.c` | INDIRECT | NO | Indirectly tested via daemon startup. Config parse errors, RFC query failures — not tested. | +| `rrd_sysinfo.c` | INDIRECT | NO | Indirectly tested via upload flow. MAC/timestamp retrieval errors — not tested. | +| `rrd_logproc.c` | INDIRECT | NO | Indirectly tested via upload flow. Log dir validation errors — not tested. | +| `rrd_archive.c` | INDIRECT | NO | Indirectly tested via upload flow. CPU throttle, archive errors — not tested. | +| `rrd_upload.c` | INDIRECT | NO | Indirectly tested via upload flow. Lock errors, cleanup failures — not tested. | +| `rrdIarmEvents.c` | PARTIAL | NO | Deep sleep event — tested. Other IARM events — not tested. | +| `uploadRRDLogs.c` | INDIRECT | NO | Entry point tested via daemon trigger. Direct API error paths — not tested. | + +### 4.2 Completely Untested Source Behaviors + +| Priority | Behavior | Source Module | Required Infrastructure | +|:---:|---|---|---| +| **P1** | WebCfg event (MsgPack decode + dispatch) | `rrdMsgPackDecoder.c`, `rrdEventProcess.c` | WebConfig mock, base64-encoded MsgPack payload | +| **P1** | C API upload error paths (NULL params, empty dir, non-existent dir) | `rrd_upload.c` | Direct C API invocation or test binary | +| **P1** | Profile data SET/GET feature documentation | `rrdInterface.c` | Feature file creation only | +| **P2** | Concurrent upload lock contention | `rrd_upload.c` | Parallel upload trigger + lock file manipulation | +| **P2** | Archive CPU usage throttle | `rrd_archive.c` | CPU load simulation | +| **P2** | Configuration fallback chain (RFC → DCM → dcm.properties) | `rrd_config.c` | Config file manipulation | +| **P2** | RDM download event with cache miss | `rrdInterface.c`, `rrdDynamic.c` | RDM mock, empty cache | +| **P3** | RBUS registration/unregistration failures | `rrdInterface.c` | RBUS mock failure injection | +| **P3** | Message queue creation failure | `rrdMain.c` | System resource exhaustion mock | +| **P3** | Event thread creation failure | `rrdMain.c` | Thread creation failure mock | +| **P3** | Directory creation/chdir failures | `rrdJsonParser.c`, `rrdRunCmdThread.c` | Filesystem permission mock | +| **P3** | systemd-run / journalctl execution failures | `rrdRunCmdThread.c` | Binary removal or mock failure | +| **P3** | Output file write errors | `rrdRunCmdThread.c` | Filesystem full or permission mock | +| **P3** | Dynamic profile JSON parse failure | `rrdDynamic.c` | Corrupted dynamic JSON file | +| **P3** | Invalid deep sleep event type | `rrdDynamic.c` | IARM event simulation with bad type | +| **P3** | Memory allocation failures (all modules) | All `.c` files | malloc failure injection (not practical in L2) | + +--- + +## 5. Gap Summary + +### 5.1 Feature vs Test Gap Table + +| Behavior Area | Feature Scenarios | Test Functions | Coverage | Top Gaps | +|---|:---:|:---:|:---:|---| +| Daemon startup/subscribe | 1 | 4 | 100% | — | +| RFC enable/disable | 2 | 1 | 100% | — | +| Single instance | 1 | 3 | 100% | — | +| Static profile report | 5 | 5 | 100% | — | +| Static category report | 5 | 5 | 100% | — | +| Static suffix report | 4 | 5 | 100% | — | +| Static suffix negative | 4 | 5 | 100% | — | +| Background command | 5 | 5 | 100% | — | +| Dynamic profile report | 5 | 9 | 100% | — | +| Dynamic subcategory | 5 | 7 | 100% | — | +| Dynamic missing | 4 | 7 | 100% | — | +| Append mode | 4 | 7 | 100% | — | +| Append (static not found) | 4 | 7 | 100% | — | +| Harmful static | 5 | 5 | 100% | — | +| Harmful dynamic | 5 | 7 | 100% | — | +| Corrupted profile | 4 | 4 | 100% | — | +| Missing command | 5 | 5 | 100% | — | +| Empty issuetype | 2 | 2 | 100% | — | +| Deep sleep | 2 | 5 | 100% | — | +| Debug report upload | 6 | 5 | 100% | — | +| **C API upload** | **21** | **5** | **~24%** | **16 scenarios not implemented** | +| **Profile data SET/GET** | **0** | **3** | **N/A** | **No feature file** | +| **WebCfg event** | **0** | **0** | **0%** | **Entire flow untested** | +| **Upload lock contention** | **0** | **0** | **0%** | **No test** | +| **Config fallback chain** | **0** | **0** | **0%** | **No test** | + +### 5.2 Action Items (Prioritized) + +| # | Priority | Action | Effort | +|:---:|:---:|---|:---:| +| 1 | P1 | Implement 16 missing `test_rrd_c_api_upload.py` test functions matching feature scenarios | High | +| 2 | P1 | Create `rrd_profile_data.feature` for the existing `test_rrd_profile_data.py` test | Low | +| 3 | P1 | Add WebCfg event L2 test (`test_rrd_webcfg_event.py` + `rrd_webcfg_event.feature`) | High | +| 4 | P2 | Add upload lock contention test | Medium | +| 5 | P2 | Add configuration fallback chain test | Medium | +| 6 | P2 | Add archive CPU throttle test | Medium | +| 7 | P2 | Add dynamic profile JSON parse failure test | Low | +| 8 | P3 | Add RBUS registration failure test | Low | +| 9 | P3 | Add systemd-run / journalctl failure test | Low | +| 10 | P3 | Add output file write error test | Low | + +--- + +## 6. Test Infrastructure Notes + +### Test Interfaces + +| Interface | Tool | Used By | +|---|---|---| +| RBUS event trigger | `rbuscli set` | All event-based tests | +| RBUS profile data | `rbuscli set/get` | `test_rrd_profile_data.py` | +| Log scraping | `grep_rrdlogs()` in `helper_functions.py` | All tests | +| File system checks | `os.path.isfile()`, `os.path.isdir()` | Prerequisite tests | +| Process control | `pidof`, `kill -9`, `nohup` | Setup/teardown | +| Mock upload server | Mock xconf server | `test_rrd_debug_report_upload.py` | +| Mock upload script | `uploadSTBLogs.sh` | Upload tests | + +### Test Execution + +- **Runner:** `pytest`, sequential per file +- **No ordering decorators:** Tests rely on file-level sequential execution order +- **Shared state:** Tests within a file depend on prior test side effects (daemon start → trigger → validate) +- **Cleanup:** Most files kill the daemon and remove logs in setup + +--- + +## 7. Appendix: Complete File Inventory + +### Feature Files (21) + +| # | File | Scenarios | +|:---:|---|:---:| +| 1 | `rrd_start_subscribe_and_wait.feature` | 1 | +| 2 | `rrd_start_control.feature` | 2 | +| 3 | `rrd_single_instance.feature` | 1 | +| 4 | `rrd_static_profile_report.feature` | 5 | +| 5 | `rrd_static_profile_category_report.feature` | 5 | +| 6 | `test_rrd_static_profile_report_with_suffix.feature` | 4 | +| 7 | `test_rrd_static_profile_report_with_suffix_negative_case.feature` | 4 | +| 8 | `rrd_background_cmd_static_profile_report.feature` | 5 | +| 9 | `rrd_dynamic_profile_report.feature` | 5 | +| 10 | `rrd_dynamic_profile_subcategory_report.feature` | 5 | +| 11 | `rrd_dynamic_profile_missing_report.feature` | 4 | +| 12 | `rrd_append_report.feature` | 4 | +| 13 | `rrd_append_dynamic_profile_static_not_found.feature` | 4 | +| 14 | `rrd_harmful_command_static_report.feature` | 5 | +| 15 | `test_rrd_dynamic_profile_harmful_report.feature` | 5 | +| 16 | `rrd_corrupted_static_profile_report.feature` | 4 | +| 17 | `rrd_static_profile_missing_command_report.feature` | 5 | +| 18 | `rrd_empty_issuetype_event.feature` | 2 | +| 19 | `rrd_deepsleep_static_report.feature` | 2 | +| 20 | `rrd_debug_report_upload.feature` | 6 | +| 21 | `rrd_c_api_upload.feature` | 21 | +| | **Total** | **97** | + +### Test Files (22) + +| # | File | Test Functions | +|:---:|---|:---:| +| 1 | `test_rrd_start_subscribe_and_wait.py` | 4 | +| 2 | `test_rrd_start_control.py` | 1 | +| 3 | `test_rrd_single_instance.py` | 3 | +| 4 | `test_rrd_static_profile_report.py` | 5 | +| 5 | `test_rrd_static_profile_category_report.py` | 5 | +| 6 | `test_rrd_static_profile_report_with_suffix.py` | 5 | +| 7 | `test_rrd_static_profile_report_with_suffix_negative_case.py` | 5 | +| 8 | `test_rrd_background_cmd_static_profile_report.py` | 5 | +| 9 | `test_rrd_dynamic_profile_report.py` | 9 | +| 10 | `test_rrd_dynamic_subcategory_report.py` | 7 | +| 11 | `test_rrd_dynamic_profile_missing_report.py` | 7 | +| 12 | `test_rrd_append_report.py` | 7 | +| 13 | `test_rrd_append_dynamic_profile_static_notfound.py` | 7 | +| 14 | `test_rrd_harmful_command_static_report.py` | 5 | +| 15 | `test_rrd_dynamic_profile_harmful_report.py` | 7 | +| 16 | `test_rrd_corrupted_static_profile_report.py` | 4 | +| 17 | `test_rrd_static_profile_missing_command_report.py` | 5 | +| 18 | `test_rrd_empty_issuetype_event.py` | 2 | +| 19 | `test_rrd_deepsleep_static_report.py` | 5 | +| 20 | `test_rrd_debug_report_upload.py` | 5 | +| 21 | `test_rrd_c_api_upload.py` | 5 | +| 22 | `test_rrd_profile_data.py` | 3 | +| | **Total** | **112** | diff --git a/test/functional-tests/features/test_rrd_static_profile_report_with_suffix.feature b/test/functional-tests/features/test_rrd_static_profile_report_with_suffix.feature new file mode 100644 index 000000000..ec8b88e15 --- /dev/null +++ b/test/functional-tests/features/test_rrd_static_profile_report_with_suffix.feature @@ -0,0 +1,48 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2018 RDK Management +# +# 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. +########################################################################## + +Feature: Remote Debugger Static Profile Report With Suffix + + Scenario: Verify remote debugger process is running + Given the remote debugger process is not running + When I start the remote debugger process + Then the remote debugger process should be running + + Scenario: Send RFC event with valid suffixed issue type + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" with value "Device.Info_ab1bghjh" + Then the event for RRD_SET_ISSUE_EVENT should be received + And the logs should contain "SUCCESS: Message sending Done" + And the logs should be seen with "SUCCESS: Message Reception Done" + And the remotedebugger request payload should include "MSG=Device.Info_ab1bghjh" + + Scenario: Validate static-profile command execution and upload flow for suffixed issue type + Given the remotedebugger received the suffixed issue type + When remotedebugger parses "/etc/rrd/remote_debugger.json" + Then remotedebugger should execute debug commands for static profile issue "Device.Info" + And the timestamped runtime service should start successfully + And the timestamped runtime journalctl collection should succeed + And the timestamped runtime service should stop successfully + And remotedebugger should generate upload archive filename for the suffixed issue type + And remotedebugger should invoke upload with the generated archive + + Scenario: Verify upload result for suffixed issue type report + When I check the upload status in the logs + Then the upload script execution should be successful + And the debug information report upload should be successful diff --git a/test/functional-tests/features/test_rrd_static_profile_report_with_suffix_negative_case.feature b/test/functional-tests/features/test_rrd_static_profile_report_with_suffix_negative_case.feature new file mode 100644 index 000000000..b8b36d2fd --- /dev/null +++ b/test/functional-tests/features/test_rrd_static_profile_report_with_suffix_negative_case.feature @@ -0,0 +1,49 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2018 RDK Management +# +# 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. +########################################################################## + +Feature: Remote Debugger Static Profile Report With Overlength Suffix + + Scenario: Verify remote debugger process is running + Given the remote debugger process is not running + When I start the remote debugger process + Then the remote debugger process should be running + + Scenario: Send RFC event with overlength suffixed issue type + Given the remote debugger is running + When I trigger the event "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType" with value "Device.Info_ab1bghjhfhk" + Then the event for RRD_SET_ISSUE_EVENT should be received + And the logs should contain "SUCCESS: Message sending Done" + And the logs should be seen with "SUCCESS: Message Reception Done" + And the remotedebugger request payload should include "MSG=Device.Info_ab1bghjhfhk" + + Scenario: Validate suffix discard and upload flow for overlength suffix + Given the remotedebugger received the overlength suffixed issue type + When remotedebugger parses "/etc/rrd/remote_debugger.json" + Then remotedebugger should execute debug commands for static profile issue "Device.Info" + And remotedebugger should log suffix-length discard with configured max length + And the timestamped runtime service should start successfully + And the timestamped runtime journalctl collection should succeed + And the timestamped runtime service should stop successfully + And remotedebugger should generate archive filename with discarded suffix + And remotedebugger should invoke upload with the generated archive + + Scenario: Verify upload result for overlength suffix report + When I check the upload status in the logs + Then the upload script execution should be successful + And the debug information report upload should be successful diff --git a/test/functional-tests/tests/helper_functions.py b/test/functional-tests/tests/helper_functions.py index 33ddd3e02..a6157e0f9 100644 --- a/test/functional-tests/tests/helper_functions.py +++ b/test/functional-tests/tests/helper_functions.py @@ -73,6 +73,21 @@ def grep_rrdlogs(search: str): print(f"Could not read file {LOG_FILE}: {e}") return search_result +def check_service_start_success(issue_type: str): + """Check that service-start logs include a success marker. + + Args: + issue_type: Issue type used in the runtime service name. + + Raises: + AssertionError: If matching service-start logs are missing or do not + include the "service success..." message. + """ + service_start = f"Starting remote_debugger_{issue_type}" + start_logs = grep_rrdlogs(service_start) + assert service_start in start_logs, f"Service start message not found for issue type '{issue_type}'" + assert "service success..." in start_logs, f"Service success marker not found for issue type '{issue_type}'" + def check_file_exists(file_path): return os.path.isfile(file_path) diff --git a/test/functional-tests/tests/test_rrd_background_cmd_static_profile_report.py b/test/functional-tests/tests/test_rrd_background_cmd_static_profile_report.py index 622128519..662ed66ab 100644 --- a/test/functional-tests/tests/test_rrd_background_cmd_static_profile_report.py +++ b/test/functional-tests/tests/test_rrd_background_cmd_static_profile_report.py @@ -108,17 +108,16 @@ def test_remote_debugger_trigger_event(): DEBUG_FILE = "Adding Details of Debug commands to Output File" assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) - SERVICE_START = f"Starting remote_debugger_{BACKGROUND_STRING} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(BACKGROUND_STRING) - JOURNAL_START = f"journalctl remote_debugger_{BACKGROUND_STRING} service success" + JOURNAL_START = f"journalctl remote_debugger_{BACKGROUND_STRING}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{BACKGROUND_STRING} service" + SERVICE_STOP = f"Stopping remote_debugger_{BACKGROUND_STRING}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_c_api_upload.py b/test/functional-tests/tests/test_rrd_c_api_upload.py index dc7f895dc..da72812d5 100644 --- a/test/functional-tests/tests/test_rrd_c_api_upload.py +++ b/test/functional-tests/tests/test_rrd_c_api_upload.py @@ -93,17 +93,16 @@ def test_remote_debugger_trigger_event(): DEBUG_FILE = "Adding Details of Debug commands to Output File" assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) - SERVICE_START = f"Starting remote_debugger_{ISSUE_STRING} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(ISSUE_STRING) - JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING} service success" + JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING} service" + SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_debug_report_upload.py b/test/functional-tests/tests/test_rrd_debug_report_upload.py index f6d798c36..51c83085c 100644 --- a/test/functional-tests/tests/test_rrd_debug_report_upload.py +++ b/test/functional-tests/tests/test_rrd_debug_report_upload.py @@ -126,17 +126,16 @@ def test_remote_debugger_trigger_event(): DEBUG_FILE = "Adding Details of Debug commands to Output File" assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) - SERVICE_START = f"Starting remote_debugger_{ISSUE_STRING} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(ISSUE_STRING) - JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING} service success" + JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING} service" + SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_deepsleep_static_report.py b/test/functional-tests/tests/test_rrd_deepsleep_static_report.py index af006e23b..a82fb3fa5 100644 --- a/test/functional-tests/tests/test_rrd_deepsleep_static_report.py +++ b/test/functional-tests/tests/test_rrd_deepsleep_static_report.py @@ -98,17 +98,16 @@ def test_remote_debugger_trigger_event(): assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) issue_string = "DEEPSLEEP" - SERVICE_START = f"Starting remote_debugger_Audio.Audio service success..." - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success("Audio.Audio") - JOURNAL_START = "journalctl remote_debugger_Audio.Audio service success..." + JOURNAL_START = "journalctl remote_debugger_Audio.Audio" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_Audio.Audio service..." + SERVICE_STOP = f"Stopping remote_debugger_Audio.Audio" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) SANITY_CHECK = "Found valid Commands" @@ -117,17 +116,16 @@ def test_remote_debugger_trigger_event(): DEBUG_FILE = "Adding Details of Debug commands to Output File" assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) - SERVICE_START = f"Starting remote_debugger_Video.Video service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success("Video.Video") - JOURNAL_START = f"journalctl remote_debugger_Video.Video service success" + JOURNAL_START = f"journalctl remote_debugger_Video.Video" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_Video.Video service" + SERVICE_STOP = f"Stopping remote_debugger_Video.Video" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_dynamic_profile_missing_report.py b/test/functional-tests/tests/test_rrd_dynamic_profile_missing_report.py index 5d8379204..aa3f3d296 100644 --- a/test/functional-tests/tests/test_rrd_dynamic_profile_missing_report.py +++ b/test/functional-tests/tests/test_rrd_dynamic_profile_missing_report.py @@ -21,6 +21,9 @@ import subprocess from helper_functions import * +MAX_START_PID_RETRIES = 5 +START_PID_RETRY_INTERVAL_SECONDS = 1 + def test_check_remote_debugger_config_file(): config_file_path = JSON_FILE assert check_file_exists(config_file_path), f"Configuration file '{config_file_path}' does not exist." @@ -32,12 +35,18 @@ def test_check_rrd_directory_exists(): def test_check_and_start_remotedebugger(): kill_rrd() remove_logfile() + remove_upload_lock() print("Starting remotedebugger process") command_to_start = "nohup /usr/local/bin/remotedebugger > /dev/null 2>&1 &" run_shell_silent(command_to_start) command_to_get_pid = "pidof remotedebugger" - pid = run_shell_command(command_to_get_pid) - assert pid != "", "remotedebugger process did not start" + pid = None + for _ in range(MAX_START_PID_RETRIES): + pid = run_shell_command(command_to_get_pid) + if pid: + break + sleep(START_PID_RETRY_INTERVAL_SECONDS) + assert pid, "remotedebugger process did not start" def reset_issuetype_rfc(): command = 'rbuscli set Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType string ""' diff --git a/test/functional-tests/tests/test_rrd_dynamic_profile_report.py b/test/functional-tests/tests/test_rrd_dynamic_profile_report.py index 7cd806795..bca831ea9 100644 --- a/test/functional-tests/tests/test_rrd_dynamic_profile_report.py +++ b/test/functional-tests/tests/test_rrd_dynamic_profile_report.py @@ -141,17 +141,16 @@ def test_check_issue_in_dynamic_profile(): assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) testrun1_string = "Test.TestRun1" - SERVICE_START = f"Starting remote_debugger_{testrun1_string} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(testrun1_string) - JOURNAL_START = f"journalctl remote_debugger_{testrun1_string} service success" + JOURNAL_START = f"journalctl remote_debugger_{testrun1_string}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{testrun1_string} service" + SERVICE_STOP = f"Stopping remote_debugger_{testrun1_string}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) TESTRUN2_MSG = "Reading Issue Type Test:TestRun2" @@ -164,17 +163,16 @@ def test_check_issue_in_dynamic_profile(): assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) testrun2_string = "Test.TestRun2" - SERVICE_START = f"Starting remote_debugger_{testrun2_string} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(testrun2_string) - JOURNAL_START = f"journalctl remote_debugger_{testrun2_string} service success" + JOURNAL_START = f"journalctl remote_debugger_{testrun2_string}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{testrun2_string} service" + SERVICE_STOP = f"Stopping remote_debugger_{testrun2_string}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_dynamic_subcategory_report.py b/test/functional-tests/tests/test_rrd_dynamic_subcategory_report.py index efec81641..389f51391 100644 --- a/test/functional-tests/tests/test_rrd_dynamic_subcategory_report.py +++ b/test/functional-tests/tests/test_rrd_dynamic_subcategory_report.py @@ -129,16 +129,15 @@ def test_check_issue_in_dynamic_profile(): EXEC_COMMANDS = 'Executing Debug Commands: ""cat /version.txt;uptime;cat /proc/buddyinfo;cat /proc/meminfo;cat /tmp/.deviceDetails.cache""' assert EXEC_COMMANDS in grep_rrdlogs(EXEC_COMMANDS) - START_SERVICE = "Starting remote_debugger_Test.TestRun1 service success..." - assert START_SERVICE in grep_rrdlogs(START_SERVICE) + check_service_start_success("Test.TestRun1") USE_JOURNALCTL = "Using journalctl to log command output..." assert USE_JOURNALCTL in grep_rrdlogs(USE_JOURNALCTL) - JOURNALCTL_SUCCESS = "journalctl remote_debugger_Test.TestRun1 service success..." + JOURNALCTL_SUCCESS = "journalctl remote_debugger_Test.TestRun1" assert JOURNALCTL_SUCCESS in grep_rrdlogs(JOURNALCTL_SUCCESS) - STOP_SERVICE = "Stopping remote_debugger_Test.TestRun1 service..." + STOP_SERVICE = "Stopping remote_debugger_Test.TestRun1" assert STOP_SERVICE in grep_rrdlogs(STOP_SERVICE) UPLOAD_START = "Starting Upload Debug output via API..." diff --git a/test/functional-tests/tests/test_rrd_static_profile_category_report.py b/test/functional-tests/tests/test_rrd_static_profile_category_report.py index 827c8770b..b0fa8abd5 100644 --- a/test/functional-tests/tests/test_rrd_static_profile_category_report.py +++ b/test/functional-tests/tests/test_rrd_static_profile_category_report.py @@ -97,17 +97,16 @@ def test_remote_debugger_trigger_event(): assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) issue_string = "Device.Info" - SERVICE_START = f"Starting remote_debugger_{issue_string} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(issue_string) - JOURNAL_START = f"journalctl remote_debugger_{issue_string} service success" + JOURNAL_START = f"journalctl remote_debugger_{issue_string}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{issue_string} service" + SERVICE_STOP = f"Stopping remote_debugger_{issue_string}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) SANITY_CHECK = "Found valid Commands" @@ -117,17 +116,16 @@ def test_remote_debugger_trigger_event(): assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) issue_string = "Device.Uptime" - SERVICE_START = f"Starting remote_debugger_{issue_string} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(issue_string) - JOURNAL_START = f"journalctl remote_debugger_{issue_string} service success" + JOURNAL_START = f"journalctl remote_debugger_{issue_string}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{issue_string} service" + SERVICE_STOP = f"Stopping remote_debugger_{issue_string}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_static_profile_report.py b/test/functional-tests/tests/test_rrd_static_profile_report.py index b6309a52e..66f336d54 100644 --- a/test/functional-tests/tests/test_rrd_static_profile_report.py +++ b/test/functional-tests/tests/test_rrd_static_profile_report.py @@ -93,17 +93,16 @@ def test_remote_debugger_trigger_event(): DEBUG_FILE = "Adding Details of Debug commands to Output File" assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) - SERVICE_START = f"Starting remote_debugger_{ISSUE_STRING} service success" - assert SERVICE_START in grep_rrdlogs(SERVICE_START) + check_service_start_success(ISSUE_STRING) - JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING} service success" + JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING}" assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) SLEEP_TIME = "Sleeping with timeout" assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) sleep(20) - SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING} service" + SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING}" assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) result = check_output_dir() diff --git a/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix.py b/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix.py new file mode 100644 index 000000000..ab9887eff --- /dev/null +++ b/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix.py @@ -0,0 +1,140 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2018 RDK Management +# +# 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. +########################################################################## + +from helper_functions import * + +def test_check_remote_debugger_config_file(): + config_file_path = JSON_FILE + assert check_file_exists(config_file_path), f"Configuration file '{config_file_path}' does not exist." + +def test_check_rrd_directory_exists(): + dir_path = OUTPUT_DIR + assert check_directory_exists(dir_path), f"Directory '{dir_path}' does not exist." + +def reset_issuetype_rfc(): + command = 'rbuscli set Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType string ""' + result = subprocess.run(command, shell=True, capture_output=True, text=True) + assert result.returncode == 0 + +def test_check_and_start_remotedebugger(): + kill_rrd() + remove_logfile() + print("Starting remotedebugger process") + command_to_start = "nohup /usr/local/bin/remotedebugger > /dev/null 2>&1 &" + run_shell_silent(command_to_start) + command_to_get_pid = "pidof remotedebugger" + pid = run_shell_command(command_to_get_pid) + assert pid != "", "remotedebugger process did not start" + +def test_remote_debugger_trigger_event(): + reset_issuetype_rfc() + sleep(10) + command = [ + 'rbuscli', 'set', + 'Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType', + 'string', 'Device.Info_ab1bghjh' + ] + result = subprocess.run(command, capture_output=True, text=True) + assert result.returncode == 0, f"Command failed with error: {result.stderr}" + + sleep(15) + + QUERY_MSG = "Received event for RRD_SET_ISSUE_EVENT" + assert QUERY_MSG in grep_rrdlogs(QUERY_MSG) + + MSG_SEND = "SUCCESS: Message sending Done" + sleep(2) + assert MSG_SEND in grep_rrdlogs(MSG_SEND) + + MSG_RECEIVE = "SUCCESS: Message Reception Done" + sleep(2) + assert MSG_RECEIVE in grep_rrdlogs(MSG_RECEIVE) + + ISSUE_MSG = f'MSG={ISSUE_STRING}' + assert ISSUE_MSG in grep_rrdlogs(ISSUE_MSG) + print("Sent and received messages are found and match in the logfile") + + READ_JSON = "Start Reading JSON File... /etc/rrd/remote_debugger.json" + assert READ_JSON in grep_rrdlogs(READ_JSON) + + PARSE_JSON = "Json File parse Success... /etc/rrd/remote_debugger.json" + assert PARSE_JSON in grep_rrdlogs(PARSE_JSON) + + if '.' in ISSUE_STRING: + ISSUE_NODE, ISSUE_SUBNODE = ISSUE_STRING.split('.') + else: + node = ISSUE_STRING + subnode = None + + ISSUE_FOUND = f"Issue Data Node: {ISSUE_NODE} and Sub-Node: {ISSUE_SUBNODE} found in Static JSON File /etc/rrd/remote_debugger.json" + + DIR_CREATION = "Creating Directory" + assert DIR_CREATION in grep_rrdlogs(DIR_CREATION) + + SANITY_CHECK = "Found valid Commands" + assert SANITY_CHECK in grep_rrdlogs(SANITY_CHECK) + + DEBUG_FILE = "Adding Details of Debug commands to Output File" + assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) + + check_service_start_success(ISSUE_STRING) + + JOURNAL_START = f"journalctl remote_debugger_{ISSUE_STRING}" + assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) + + SLEEP_TIME = "Sleeping with timeout" + assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) + sleep(20) + + SERVICE_STOP = f"Stopping remote_debugger_{ISSUE_STRING}" + assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) + + GENERATE_FILE = f"Generated filename: AABBCCDDEEFF_DEVICE_INFO_AB1BGHJH" + assert GENERATE_FILE in grep_rrdlogs(GENERATE_FILE) + + START_UPLOAD = f"Starting upload - server: mockxconf, protocol: HTTP, file: AABBCCDDEEFF_DEVICE_INFO_AB1BGHJH" + assert START_UPLOAD in grep_rrdlogs(START_UPLOAD) + result = check_output_dir() + print(result) + + UPLOAD_LOGS = "Starting Upload Debug output via API" + assert UPLOAD_LOGS in grep_rrdlogs(UPLOAD_LOGS) + +def test_remotedebugger_upload_report(): + UPLOAD_SUCCESS = "RRD Upload Script Execution Success" + UPLOAD_FAILURE = "RRD Upload Script Execution Failure" + if UPLOAD_SUCCESS in grep_rrdlogs(UPLOAD_SUCCESS): + print("Upload success") + elif UPLOAD_FAILURE in grep_rrdlogs(UPLOAD_FAILURE): + print("Upload failed") + else: + print("Upload status not found in logs") + + SCRIPT_SUCCESS = "Debug Information Report upload Failed" + SCRIPT_FAILURE = "Debug Information Report upload Success" + if SCRIPT_SUCCESS in grep_rrdlogs(SCRIPT_SUCCESS): + print("Script execution success") + elif SCRIPT_FAILURE in grep_rrdlogs(SCRIPT_FAILURE): + print("Script execution failed") + else: + print("Script execution not found in logs") + + remove_logfile() + remove_outdir_contents(OUTPUT_DIR) + kill_rrd() diff --git a/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix_negative_case.py b/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix_negative_case.py new file mode 100644 index 000000000..8366dd4e0 --- /dev/null +++ b/test/functional-tests/tests/test_rrd_static_profile_report_with_suffix_negative_case.py @@ -0,0 +1,144 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2018 RDK Management +# +# 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. +########################################################################## + +from helper_functions import * + +def test_check_remote_debugger_config_file(): + config_file_path = JSON_FILE + assert check_file_exists(config_file_path), f"Configuration file '{config_file_path}' does not exist." + +def test_check_rrd_directory_exists(): + dir_path = OUTPUT_DIR + assert check_directory_exists(dir_path), f"Directory '{dir_path}' does not exist." + +def reset_issuetype_rfc(): + command = 'rbuscli set Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType string ""' + result = subprocess.run(command, shell=True, capture_output=True, text=True) + assert result.returncode == 0 + +def test_check_and_start_remotedebugger(): + kill_rrd() + remove_logfile() + print("Starting remotedebugger process") + command_to_start = "nohup /usr/local/bin/remotedebugger > /dev/null 2>&1 &" + run_shell_silent(command_to_start) + command_to_get_pid = "pidof remotedebugger" + pid = run_shell_command(command_to_get_pid) + assert pid != "", "remotedebugger process did not start" + +def test_remote_debugger_trigger_event(): + reset_issuetype_rfc() + sleep(10) + ISSUE_STRING = "Device.Info_ab1bghjhfhk" + command = [ + 'rbuscli', 'set', + 'Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.IssueType', + 'string', ISSUE_STRING + ] + result = subprocess.run(command, capture_output=True, text=True) + assert result.returncode == 0, f"Command failed with error: {result.stderr}" + + sleep(15) + + QUERY_MSG = "Received event for RRD_SET_ISSUE_EVENT" + assert QUERY_MSG in grep_rrdlogs(QUERY_MSG) + + MSG_SEND = "SUCCESS: Message sending Done" + sleep(2) + assert MSG_SEND in grep_rrdlogs(MSG_SEND) + + MSG_RECEIVE = "SUCCESS: Message Reception Done" + sleep(2) + assert MSG_RECEIVE in grep_rrdlogs(MSG_RECEIVE) + + ISSUE_MSG = f'MSG={ISSUE_STRING}' + assert ISSUE_MSG in grep_rrdlogs(ISSUE_MSG) + print("Sent and received messages are found and match in the logfile") + + READ_JSON = "Start Reading JSON File... /etc/rrd/remote_debugger.json" + assert READ_JSON in grep_rrdlogs(READ_JSON) + + PARSE_JSON = "Json File parse Success... /etc/rrd/remote_debugger.json" + assert PARSE_JSON in grep_rrdlogs(PARSE_JSON) + + if '.' in ISSUE_STRING: + ISSUE_NODE, ISSUE_SUBNODE = ISSUE_STRING.split('.') + else: + node = ISSUE_STRING + subnode = None + + ISSUE_FOUND = f"Issue Data Node: {ISSUE_NODE} and Sub-Node: {ISSUE_SUBNODE} found in Static JSON File /etc/rrd/remote_debugger.json" + + DIR_CREATION = "Creating Directory" + assert DIR_CREATION in grep_rrdlogs(DIR_CREATION) + + SANITY_CHECK = "Found valid Commands" + assert SANITY_CHECK in grep_rrdlogs(SANITY_CHECK) + + DEBUG_FILE = "Adding Details of Debug commands to Output File" + assert DEBUG_FILE in grep_rrdlogs(DEBUG_FILE) + + check_service_start_success("Device.Info") + + JOURNAL_START = f"journalctl remote_debugger_Device.Info" + assert JOURNAL_START in grep_rrdlogs(JOURNAL_START) + + SLEEP_TIME = "Sleeping with timeout" + assert SLEEP_TIME in grep_rrdlogs(SLEEP_TIME) + sleep(20) + + SERVICE_STOP = f"Stopping remote_debugger_Device.Info" + assert SERVICE_STOP in grep_rrdlogs(SERVICE_STOP) + + SUFFIX_EXCEEDS = f"Suffix after 'Device.Info' exceeds max length (12 > 9); discarding" + assert SUFFIX_EXCEEDS in grep_rrdlogs(SUFFIX_EXCEEDS) + + GENERATE_FILE = f"Generated filename: AABBCCDDEEFF_DEVICE_INFO_" + assert GENERATE_FILE in grep_rrdlogs(GENERATE_FILE) + + START_UPLOAD = f"Starting upload - server: mockxconf, protocol: HTTP, file: AABBCCDDEEFF_DEVICE_INFO_" + assert START_UPLOAD in grep_rrdlogs(START_UPLOAD) + result = check_output_dir() + print(result) + + UPLOAD_LOGS = "Starting Upload Debug output via API" + assert UPLOAD_LOGS in grep_rrdlogs(UPLOAD_LOGS) + +def test_remotedebugger_upload_report(): + UPLOAD_SUCCESS = "RRD Upload Script Execution Success" + UPLOAD_FAILURE = "RRD Upload Script Execution Failure" + if UPLOAD_SUCCESS in grep_rrdlogs(UPLOAD_SUCCESS): + print("Upload success") + elif UPLOAD_FAILURE in grep_rrdlogs(UPLOAD_FAILURE): + print("Upload failed") + else: + print("Upload status not found in logs") + + SCRIPT_SUCCESS = "Debug Information Report upload Success" + SCRIPT_FAILURE = "Debug Information Report upload Failed" + if SCRIPT_SUCCESS in grep_rrdlogs(SCRIPT_SUCCESS): + print("Script execution success") + elif SCRIPT_FAILURE in grep_rrdlogs(SCRIPT_FAILURE): + print("Script execution failed") + else: + print("Script execution not found in logs") + + remove_logfile() + remove_outdir_contents(OUTPUT_DIR) + kill_rrd()