Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
target-branch: dev
schedule:
interval: weekly
ignore:
# The cross-repository bootstrap and workflow are reviewed together.
- dependency-name: eclipse-threadx/threadx/.github/workflows/regression_template.yml
43 changes: 23 additions & 20 deletions .github/workflows/regression_test.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,47 @@
name: USBX Regression Test

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
workflow_dispatch:
inputs:
tests_to_run:
description: 'all, single or multiple of default_build_coverage error_check_build_full_coverage tracex_enable_build device_buffer_owner_build device_zero_copy_build nofx_build_coverage optimized_build standalone_device_build_coverage standalone_device_buffer_owner_build standalone_device_zero_copy_build standalone_host_build_coverage standalone_build_coverage generic_build otg_support_build memory_management_build_coverage msrc_rtos_build msrc_standalone_build'
required: false
description: 'all, or space-separated profiles from test/cmake/usbx/CMakeLists.txt'
required: true
default: 'all'
type: string
skip_coverage:
required: false
type: boolean
default: false
coverage_name:
required: false
default: 'default_build_coverage'
push:
branches: [ master ]
branches: [dev, master]
pull_request:
branches: [ master ]
branches: [dev, master]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
concurrency:
group: usbx-regression-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
manual_tests:
if: github.event_name == 'workflow_dispatch'
permissions:
contents: read
issues: read
checks: write
pull-requests: write
# GitHub validates deployment permissions even when that job is skipped.
# The called test job narrows its own token scope.
pages: write
id-token: write

uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@master
uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@b37cd4a81a1cb8c2ebefc438220ab7f009e13362
with:
cmake_path: ./test/cmake/usbx
build_script: ./scripts/build.sh ${{ inputs.tests_to_run }}
test_script: ./scripts/test.sh ${{ inputs.tests_to_run }}
coverage_name: ${{ inputs.coverage_name }}
skip_coverage: ${{ !!inputs.skip_coverage }}
build_script: ./scripts/build.sh --manual
test_script: ./scripts/test.sh --manual
coverage_name: ${{ inputs.tests_to_run == 'all' && 'merged' || 'subset' }}
coverage_thresholds: '0 100'
skip_coverage: ${{ inputs.skip_coverage }}
skip_deploy: ${{ github.ref != 'refs/heads/master' || inputs.tests_to_run != 'all' }}

auto_tests:
if: github.event_name != 'workflow_dispatch'
Expand All @@ -49,13 +50,15 @@ jobs:
issues: read
checks: write
pull-requests: write
# The reusable workflow declares a separate deployment job.
pages: write
id-token: write

uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@master
uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@b37cd4a81a1cb8c2ebefc438220ab7f009e13362
with:
cmake_path: ./test/cmake/usbx
build_script: ./scripts/build.sh all
test_script: ./scripts/test.sh all
coverage_name: default_build_coverage
coverage_name: merged
coverage_thresholds: '66 100'
skip_coverage: false
skip_deploy: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ compile_commands.json
CTestTestfile.cmake

coverage_report/

/test/externals/
/test/cmake/usbx/.run.sh
/test/cmake/usbx/libs/
__pycache__/
2 changes: 2 additions & 0 deletions samples/demo_usbx.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
/* */
/* SPDX-License-Identifier: MIT */
/***************************************************************************/
/* Portions of this file were generated with AI assistance. */

/* This is a small demo of the USBX */

#include "ux_api.h"
#include "ux_device_stack.h"
#include "ux_system.h"
#include "ux_utility.h"
#include "ux_host_class_dpump.h"
Expand Down
8 changes: 7 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@
# SPDX-License-Identifier: MIT
##############################################################################

$(dirname `realpath $0`)/../test/cmake/usbx/run.sh build $@
set -euo pipefail
export CC=${CC:-gcc-14}
export GCOV=${GCOV:-gcov-14}
export CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL:-4}
"$(dirname "$(realpath "$0")")/../test/cmake/usbx/run.sh" build "$@"
echo "Checking runner and coverage failure handling in isolated fixtures."
python3 "$(dirname "$(realpath "$0")")/../test/cmake/usbx/test_runner.py"
71 changes: 47 additions & 24 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,50 @@
# SPDX-License-Identifier: MIT
##############################################################################

#
# Install necessary softwares for Ubuntu.

sudo apt-get update
sudo apt-get install -y \
gcc-multilib \
git \
g++ \
python3-pip \
ninja-build \
unifdef \
p7zip-full \
tofrodos \
gawk \
software-properties-common

wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
CODENAME=$(lsb_release -c | cut -f2 -d':' | sed 's/\t//')
sudo apt-add-repository -y "deb https://apt.kitware.com/ubuntu/ $CODENAME main"
sudo apt-get -y install cmake

python3 -m pip install --upgrade pip
pip3 install gcovr==4.1
pip3 install --upgrade cmake
set -euo pipefail

retry() {
local attempt
for attempt in 1 2 3; do
if "$@"; then
return 0
fi
if [ "$attempt" -lt 3 ]; then
sleep $((attempt * 5))
fi
done
return 1
}

apt_options=(-o Acquire::Retries=3 -o DPkg::Lock::Timeout=60)
if ! retry sudo timeout 150 apt-get "${apt_options[@]}" update; then
echo "Package index update failed; package installation will verify availability." >&2
fi
retry sudo timeout 150 apt-get "${apt_options[@]}" install -y \
cmake gcc-14 gcc-14-multilib git ninja-build python3-venv \
unifdef p7zip-full tofrodos gawk

venv_dir="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/usbx-ci-venv"
python3 -m venv "$venv_dir"
retry timeout 120 "$venv_dir/bin/python" -m pip install \
--retries 3 --timeout 30 gcovr==8.6

cc="${CC:-gcc-14}"
gcov="${GCOV:-gcov-14}"
cc_version=$("$cc" -dumpfullversion)
gcov_version=$("$gcov" --version | sed -n '1{s/.* \([0-9][0-9]*\.[0-9][0-9]*\(\.[0-9][0-9]*\)\?\).*/\1/p;}')
if [ -z "$gcov_version" ] || [ "$cc_version" != "$gcov_version" ]; then
echo "Compiler $cc and coverage tool $gcov have different versions." >&2
exit 1
fi

if [ -n "${GITHUB_ENV:-}" ]; then
printf 'CC=%s\nGCOV=%s\n' "$cc" "$gcov" >> "$GITHUB_ENV"
printf '%s\n' "$venv_dir/bin" >> "$GITHUB_PATH"
fi

"$venv_dir/bin/gcovr" --version | head -1
"$cc" --version | head -1
"$gcov" --version | head -1

printf 'Coverage environment: %s\n' "$venv_dir/bin/activate"
8 changes: 7 additions & 1 deletion scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@
# SPDX-License-Identifier: MIT
##############################################################################

CTEST_PARALLEL_LEVEL=4 $(dirname `realpath $0`)/../test/cmake/usbx/run.sh test $@
set -euo pipefail
export CTEST_PARALLEL_LEVEL=${CTEST_PARALLEL_LEVEL:-4}
export CTEST_REPEAT_FAIL=1
export CC=${CC:-gcc-14}
export GCOV=${GCOV:-gcov-14}
export CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL:-4}
exec "$(dirname "$(realpath "$0")")/../test/cmake/usbx/run.sh" test "$@"
40 changes: 40 additions & 0 deletions test/cmake/usbx/BASELINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# GCC 14 baseline

Measured at USBX commit `ffc0f79d951c716a03addcf7255020328e0d5b47`, with the
dependency revisions in `dependencies.txt`, GCC/gcov 14.3.0 and gcovr 8.6.
All 17 configurations were configured and built from clean trees with CMake and
Ninja. CTest ran every configuration once, including configurations with build
failures: 3,019/3,043 cases passed in 666.17 seconds of summed CTest wall time.

Twenty-three cases could not run because five regression sources had incompatible
pointer arguments or missing declarations. The sample also lacked device-stack
declarations. One standalone printer transfer assertion failed. These results
are retained as failures; a subsequent isolated printer pass does not erase them.

Coverage below uses the existing profile-specific exclusions, so percentages are
not comparable across profiles and cannot be averaged or merged. A dash means
the profile was not instrumented. Standalone-device collection initially failed
gcovr's 2^32 suspicious-hit heuristic; the same raw counters were then collected
with the documented 2^40 threshold and unchanged exclusions. No tests were rerun
to produce that coverage. Build times are Ninja execution spans and exclude
configuration and dependency setup.

| Profile | Passed / tests | Build s | Test s | Lines hit / valid | Branches hit / valid |
| --- | ---: | ---: | ---: | ---: | ---: |
| default_build_coverage | 427/430 | 10.838 | 13.89 | 7701/8014 | 3420/3738 |
| error_check_build_full_coverage | 425/430 | 11.190 | 13.53 | 14767/24084 | 6238/11250 |
| tracex_enable_build | 427/430 | 11.390 | 13.48 | — | — |
| device_buffer_owner_build | 427/430 | 11.371 | 11.18 | — | — |
| device_zero_copy_build | 427/430 | 11.271 | 16.53 | — | — |
| nofx_build_coverage | 46/46 | 6.778 | 5.57 | 2751/7955 | 993/3738 |
| optimized_build | 129/129 | 6.893 | 3.46 | — | — |
| standalone_device_build_coverage | 55/57 | 6.669 | 185.88 | 2625/3098 | 880/1279 |
| standalone_device_buffer_owner_build | 56/57 | 6.782 | 185.39 | — | — |
| standalone_device_zero_copy_build | 56/57 | 6.896 | 177.48 | — | — |
| standalone_host_build_coverage | 44/44 | 6.633 | 20.78 | 4005/5255 | 1464/2289 |
| standalone_build_coverage | 20/20 | 5.412 | 0.25 | 2011/8277 | 720/3567 |
| generic_build | 1/1 | 6.047 | 0.01 | — | — |
| otg_support_build | 427/430 | 10.614 | 11.31 | — | — |
| memory_management_build_coverage | 11/11 | 6.619 | 4.63 | 4748/8069 | 1715/3780 |
| msrc_rtos_build | 33/33 | 6.482 | 0.76 | — | — |
| msrc_standalone_build | 8/8 | 5.069 | 2.04 | — | — |
12 changes: 9 additions & 3 deletions test/cmake/usbx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ cmake_policy(SET CMP0077 NEW)

project(usbx_test LANGUAGES C)

option(USBX_CI_COVERAGE "Instrument every selected configuration" "$ENV{TX_COVERAGE}")

# Use customized ux_user.h
set(UX_USER_FILE ${CMAKE_CURRENT_SOURCE_DIR}/ux_user.h)

Expand Down Expand Up @@ -215,7 +217,7 @@ else()
message(STATUS "Building usbx BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
endif()
if(NOT BUILD_SHARED_LIBS)
if(CMAKE_BUILD_TYPE MATCHES ".*_coverage")
if(USBX_CI_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage")
add_link_options(-fprofile-arcs)
add_link_options(-lgcov)
endif()
Expand Down Expand Up @@ -255,13 +257,17 @@ if(NOT (CMAKE_BUILD_TYPE MATCHES "standalone.*"))
endif()

# Coverage
if(CMAKE_BUILD_TYPE MATCHES ".*_coverage")
if(USBX_CI_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage")
target_compile_options(usbx PRIVATE -fprofile-arcs -ftest-coverage)
target_link_options(usbx PRIVATE -fprofile-arcs -ftest-coverage)
endif()

# Build ThreadX library once
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run.sh build_libs)
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run.sh build_libs
RESULT_VARIABLE dependency_status)
if(NOT dependency_status EQUAL 0)
message(FATAL_ERROR "Dependency build failed: ${dependency_status}")
endif()
add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run.sh
build_libs)

Expand Down
62 changes: 62 additions & 0 deletions test/cmake/usbx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Linux regression and coverage

The regression workflow builds and tests all 17 profiles on every pull request to
`dev` or `master`. Its check name is `auto_tests / run_tests`. Workflow
success alone does not enforce merging: repository rules must separately require
that check. The staged Otterdog ruleset must remain disabled until this workflow
is available on `dev`.

Run `scripts/install.sh` on Ubuntu 24.04, then use GCC 14 and its matching gcov with
CMake and Ninja:

```sh
TX_COVERAGE=ON ./scripts/build.sh all
TX_COVERAGE=ON ./scripts/test.sh all
python3 test/cmake/usbx/test_runner.py
```

The installer installs gcovr 8.6 in a virtual environment and exports its location
through GitHub Actions environment files. Outside Actions, activate the reported
virtual environment before running coverage. `CC` and `GCOV` can select matching
compiler and coverage executables. The default build and test concurrency is four.
CTest runs each case once; failing cases are not retried into a passing result.

`dependencies.txt` records immutable ThreadX, FileX and NetX Duo revisions. FileX
PR #106 is open at the pinned revision; this setup does not assume it has merged.
Existing dependency checkouts must be clean and match their pins. Remove an old
checkout explicitly when updating a pin. The ThreadX bootstrap supplies JUnit
output without runtime edits to dependency files.

Pass profile names instead of `all` to either script, or select them in the manual
workflow. Build and test output names the exact selection. JUnit files, test logs
and `build/results.txt` report actual counts and elapsed test times. The generic
profile's single CTest case is a build-only placeholder, not a functional USB test.

Every selected profile is instrumented when `TX_COVERAGE=ON`, including the generic
build. Raw JSON, Cobertura XML and HTML use one inclusion policy: all compiled C
sources matching `common/*/src/*.c`. Host controllers, simulators, optional classes
and untested source lines remain in the denominator. Dependency and test code are
outside that policy. No profile-specific diagnostic exclusion affects the union.

Each input must have nonempty JSON, XML and HTML, measured source lines, and
repository-relative paths. The merger checks that JSON and XML agree and that its
measured and covered source-line sets equal the exact unions of its inputs.
Reports and execution counters are cleared before each test selection. A subset
cannot consume an earlier full run's reports or publish complete coverage.
Available diagnostics and coverage survive failing tests; missing coverage itself
fails the runner. Full-suite line and branch floors are in `coverage-floors.json`.
Standalone polling produces more than six billion positive hits in a single
profile. Direct GCC 14 output confirms balanced loop counts. Collection uses a
2^40 suspicious-hit threshold instead of gcovr's default 2^32 heuristic; negative
counts and parse errors remain fatal, and no lines are ignored to obtain a report.

Only successful complete runs on `master` can deploy Pages. Callers grant the
permissions required for GitHub to validate the pinned reusable workflow's
separate deployment job; its test job retains narrower permissions. The template
bounds installation, build and test steps and uploads diagnostics on failure.
The manual selector is parsed from the event file, never interpolated as shell
code. All pull requests retain the full matrix; there is no conditional routing.

Dependabot targets `dev` once its configuration is present on the default branch.
The cross-repository workflow/bootstrap revision requires a separate review and
is excluded from routine action updates.
Loading