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
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: dev
93 changes: 84 additions & 9 deletions .github/workflows/regression_test.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,100 @@
# This is a basic workflow that is manually triggered

name: 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:
push:
branches: [ master ]
branches: [dev, master]
pull_request:
branches: [ master ]
branches: [dev, master]

concurrency:
group: filex-regression-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "linux_job"
run_tests:
permissions:
contents: read
issues: read
checks: write
pull-requests: write
# GitHub validates the called workflow's skipped deploy job permissions.
# Its test job narrows its own token permissions.
pages: write
id-token: write
uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@b37cd4a81a1cb8c2ebefc438220ab7f009e13362
with:
coverage_name: merged
coverage_thresholds: '99 100'
skip_deploy: true

win64:
runs-on: windows-2022
timeout-minutes: 90
permissions:
contents: read
steps:
- name: Check out FileX
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check out ThreadX
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: eclipse-threadx/threadx
ref: b37cd4a81a1cb8c2ebefc438220ab7f009e13362
path: threadx
- name: Build Win64 configurations
timeout-minutes: 35
shell: pwsh
run: |
Start-Transcript -Path win64-build.log
try {
./scripts/build_fx.ps1 -Arch win64 -Configuration all -ThreadXDir "$env:GITHUB_WORKSPACE/threadx" -BuildTimeoutSeconds 300
} finally {
Stop-Transcript
}
- name: Test Win64 configurations
timeout-minutes: 50
shell: pwsh
run: |
Start-Transcript -Path win64-test.log
try {
./scripts/test_fx.ps1 -Arch win64 -Configuration all -Parallel 1 -TestTimeoutSeconds 600
} finally {
Stop-Transcript
}
- name: Upload failure diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: win64-diagnostics
path: |
win64-build.log
win64-test.log
build/tests/win64/**/Testing/Temporary/*
build/tests/win64/**/CMakeFiles/CMakeConfigureLog.yaml
if-no-files-found: warn

deploy:
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' }}
needs: [run_tests, win64]
runs-on: ubuntu-24.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@master
steps:
- name: Download merged coverage
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: coverage_report-*
merge-multiple: true
path: coverage_report
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: coverage_report/merged
- name: Deploy coverage
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
2 changes: 1 addition & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# SPDX-License-Identifier: MIT
##############################################################################

$(dirname `realpath $0`)/../test/cmake/run.sh build all
"$(dirname "$(realpath "$0")")/../test/cmake/run.sh" build all
62 changes: 40 additions & 22 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,48 @@
# SPDX-License-Identifier: MIT
##############################################################################

#
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
}

# Remove large folder
rm -rf /opt/hostedtoolcache
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

# Install necessary softwares for Ubuntu.
venv_dir="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/filex-ci-venv"
python3 -m venv "$venv_dir"
retry timeout 120 "$venv_dir/bin/python" -m pip install \
--retries 3 --timeout 30 gcovr==8.6

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
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

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//')
apt-add-repository "deb https://apt.kitware.com/ubuntu/ $CODENAME main"
if [ -n "${GITHUB_ENV:-}" ]; then
printf 'CC=%s\nGCOV=%s\n' "$cc" "$gcov" >> "$GITHUB_ENV"
printf '%s\n' "$venv_dir/bin" >> "$GITHUB_PATH"
fi

python3 -m pip install --upgrade pip
pip3 install gcovr==4.1
pip install --upgrade cmake
"$venv_dir/bin/gcovr" --version | head -1
"$cc" --version | head -1
"$gcov" --version | head -1
4 changes: 3 additions & 1 deletion scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
# SPDX-License-Identifier: MIT
##############################################################################

$(dirname `realpath $0`)/../test/cmake/run.sh test all
export CTEST_PARALLEL_LEVEL="${CTEST_PARALLEL_LEVEL:-1}"
export CTEST_REPEAT_FAIL="${CTEST_REPEAT_FAIL:-1}"
"$(dirname "$(realpath "$0")")/../test/cmake/run.sh" test all
13 changes: 9 additions & 4 deletions test/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ endif()

project(filex_test LANGUAGES C)

option(FX_COVERAGE "Instrument FileX in every Linux test configuration" OFF)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
Expand Down Expand Up @@ -92,15 +94,19 @@ add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../.. filex)
add_subdirectory(regression)
add_subdirectory(samples)

# Coverage instrumentation (GCC/Clang only — not supported by MSVC)
if(NOT MSVC AND CMAKE_BUILD_TYPE MATCHES ".*_coverage")
# TX_COVERAGE is supplied by the ThreadX regression runner.
if(NOT MSVC AND (FX_COVERAGE OR TX_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage"))
target_compile_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
target_link_options(filex PRIVATE -fprofile-arcs -ftest-coverage)
endif()

# Build ThreadX shared library for Linux
if(NOT MSVC AND NOT FX_STANDALONE_ENABLE)
execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs)
execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs
RESULT_VARIABLE threadx_build_result)
if(NOT threadx_build_result STREQUAL "0")
message(FATAL_ERROR "ThreadX dependency build failed: ${threadx_build_result}")
endif()
add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh
build_libs)
add_dependencies(filex build_libs)
Expand Down Expand Up @@ -135,4 +141,3 @@ else()
-Waggregate-return
-Wfloat-equal)
endif()

139 changes: 134 additions & 5 deletions test/cmake/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,139 @@
# SPDX-License-Identifier: MIT
##############################################################################

set -euo pipefail

set -e
cd "$(dirname "$0")"
repo_root=$(cd ../.. && pwd)
report_dir=coverage_report
configurations=(
default_build_coverage no_cache_build no_cache_standalone_build
fault_tolerant_build_coverage no_check_build no_cache_fault_tolerant_build
standalone_build_coverage standalone_fault_tolerant_build_coverage
standalone_no_cache_fault_tolerant_build
)

cd $(dirname $0)
mkdir -p coverage_report/$1
gcovr --object-directory=build/$1/filex/CMakeFiles/filex.dir/common/src -r ../../common/src -e ".*driver.*" --xml-pretty --output coverage_report/$1.xml
gcovr --object-directory=build/$1/filex/CMakeFiles/filex.dir/common/src -r ../../common/src -e ".*driver.*" --html --html-details --output coverage_report/$1/index.html
check_report() {
python3 - "$@" <<'PY'
import json
import pathlib
import sys
import xml.etree.ElementTree as ET

report = pathlib.Path(sys.argv[1])
root = ET.parse(report).getroot()
classes = root.findall('.//class')
if int(root.get('lines-valid', '0')) == 0 or not classes:
raise SystemExit(f'{report}: report contains no measured FileX files')
if any(not item.get('filename', '').startswith('common/src/') for item in classes):
raise SystemExit(f'{report}: report contains a source outside common/src')
if len(sys.argv) > 2:
with open(sys.argv[2], encoding='utf-8') as stream:
data = json.load(stream)
if not data.get('files'):
raise SystemExit(f'{sys.argv[2]}: tracefile contains no measured files')
if any(not item.get('file', '').startswith('common/src/') for item in data['files']):
raise SystemExit(f'{sys.argv[2]}: tracefile contains a source outside common/src')
print(f"{report}: lines {root.get('lines-covered')}/{root.get('lines-valid')} "
f"({float(root.get('line-rate', '0')) * 100:.2f}%), branches "
f"{root.get('branches-covered')}/{root.get('branches-valid')} "
f"({float(root.get('branch-rate', '0')) * 100:.2f}%)")
PY
}

check_merged_floor() {
python3 - "$1" <<'PY'
import sys
import xml.etree.ElementTree as ET

root = ET.parse(sys.argv[1]).getroot()
for label, attribute, minimum in (
('line', 'lines', 999),
('branch', 'branches', 994),
):
covered = int(root.get(f'{attribute}-covered', '0'))
valid = int(root.get(f'{attribute}-valid', '0'))
if valid == 0 or covered * 1000 < valid * minimum:
raise SystemExit(
f'{sys.argv[1]}: {label} coverage {covered}/{valid} '
f'is below {minimum / 10:.1f}%'
)
PY
}

if [ "${1:-}" = --clean ]; then
if [ -d "$report_dir" ]; then
rm -r -- "$report_dir"
fi
if [ -d build ]; then
find build -type f -name '*.gcda' -delete
fi
exit 0
fi

if [ "${1:-}" = --merge ]; then
trace_args=()
for configuration in "${configurations[@]}"; do
base="$report_dir/per_configuration/$configuration"
for path in "$base.json" "$base.xml" "$base/index.html"; do
if [ ! -s "$path" ]; then
echo "Missing or empty coverage report: $path" >&2
exit 1
fi
done
check_report "$base.xml" "$base.json"
trace_args+=(--add-tracefile "$base.json")
done

mkdir -p "$report_dir/merged"
gcovr -r "$repo_root" "${trace_args[@]}" --xml-pretty \
--output "$report_dir/merged.xml"
gcovr -r "$repo_root" "${trace_args[@]}" --html --html-details \
--output "$report_dir/merged/index.html"
check_report "$report_dir/merged.xml"
check_merged_floor "$report_dir/merged.xml"
exit 0
fi

configuration="${1:-}"
valid=0
for item in "${configurations[@]}"; do
if [ "$configuration" = "$item" ]; then
valid=1
break
fi
done
if [ "$valid" -ne 1 ]; then
echo "Unknown coverage configuration: $configuration" >&2
exit 1
fi

cc_name=$(basename "${CC:-gcc}")
if [ -n "${GCOV:-}" ]; then
gcov="$GCOV"
elif [[ "$cc_name" = gcc* ]]; then
gcov="gcov${cc_name#gcc}"
else
gcov=gcov
fi
if ! command -v "$gcov" >/dev/null 2>&1; then
echo "Coverage tool $gcov is unavailable." >&2
exit 1
fi

objects="$PWD/build/$configuration/filex/CMakeFiles/filex.dir/common/src"
if [ ! -d "$objects" ] || [ -z "$(find "$objects" -name '*.gcda' -print -quit)" ]; then
echo "No FileX coverage data for $configuration." >&2
exit 1
fi

base="$report_dir/per_configuration/$configuration"
mkdir -p "$base"
# Regression tests use a separate driver with fault injection.
gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \
-e "$repo_root/common/src/fx_ram_driver.c" \
"$objects" --json "$base.json" --xml-pretty --output "$base.xml"
gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \
-e "$repo_root/common/src/fx_ram_driver.c" \
"$objects" --html --html-details --output "$base/index.html"
check_report "$base.xml" "$base.json"
1 change: 1 addition & 0 deletions test/cmake/regression/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ set(regression_test_cases
${SOURCE_DIR}/filex_file_write_notify_test.c
${SOURCE_DIR}/filex_file_write_available_cluster_test.c
${SOURCE_DIR}/filex_utility_test.c
${SOURCE_DIR}/filex_partition_offset_extended_test.c
${SOURCE_DIR}/filex_utility_fat_flush_test.c)

add_library(test_utility ${SOURCE_DIR}/fx_ram_driver_test.c
Expand Down
Loading