Skip to content

Move tests to relevant subprojects - #615

Merged
KenVanHoeylandt merged 8 commits into
mainfrom
move-tests
Aug 13, 2026
Merged

Move tests to relevant subprojects#615
KenVanHoeylandt merged 8 commits into
mainfrom
move-tests

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Aug 13, 2026

Copy link
Copy Markdown
Contributor
  • Moved test projects to the parent project they belong to
  • Improved test stability/corectness
  • Improved recursive directory deletion by safely ignoring current- and parent-directory entries.
  • Update docs

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updated CMake test discovery, reconfiguration behavior, output paths, and test cleanup. Added FreeRTOS-based doctest runners. Added coverage for application management and events, cryptography, services, platform utilities, FreeRTOS primitives, kernel resources, persistence, timing, and system events. Updated test synchronization, mutex cleanup, atomic state handling, and task-creation failure handling.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: moving and organizing tests within their relevant subprojects.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch move-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (5)
Modules/app-module/tests/source/app_manager_test.cpp (1)

393-396: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These two test cases depend on global state left by earlier test cases.

Line 396 requires that no app instance is Active. Line 441 has the same requirement. Both hold only when every earlier TEST_CASE stopped its instances and doctest runs the cases in declaration order. The checks fail if a developer runs a single case with --test-case=..., or randomizes with --order-by=rand.

Consider adding an explicit precondition helper that stops all remaining instances at the start of these cases, so they pass in isolation.

Modules/service-module/tests/source/service_test.cpp (1)

5-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This declaration is unused and its comment is stale.

No test case in this file calls service_instance_set_state, and no test exercises try_get/put gating. Remove the declaration, or add the test it was added for.

TactilityKernel/tests/source/time_and_delay.cpp (1)

5-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Loosen the upper bounds to avoid flaky timing tests.

The three tests require the measured delay to stay within one tick of the requested delay. The POSIX FreeRTOS simulator runs on a preemptible host scheduler, so a loaded CI machine can add several ticks of jitter. The lower bounds prove the delay APIs do not return early, which is the useful guarantee. Consider a larger tolerance for the upper bounds.

TactilityKernel/tests/source/preferences_test.cpp (1)

53-60: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Require a non-null Preferences before using it.

Most test cases call preferences_open and then pass the result straight into preferences_put_*. If preferences_open returns nullptr, the test crashes instead of reporting a failure. Line 45 uses CHECK_NE, which records the failure but still continues into the dereference. Use REQUIRE_NE after each preferences_open call, as lines 224 and 234 already do.

Proposed fix
     Preferences* preferences = preferences_open(TEST_PATH);
+    REQUIRE_NE(preferences, nullptr);
     preferences_put_bool(preferences, "flag", true);
TactilityKernel/tests/source/properties_file_test.cpp (1)

209-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Require a non-null PropertiesFile before using it.

Line 210 and line 219 pass the properties_file_open result straight into properties_file_set. If the open fails, the test crashes instead of reporting a failure. Add REQUIRE_NE(file, nullptr) after these properties_file_open calls, as line 188 already does.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c7ae0356-ff15-46f5-a6d3-620b6dcab6ea

📥 Commits

Reviewing files that changed from the base of the PR and between d6b1d15 and 0d86b01.

📒 Files selected for processing (60)
  • Documentation/ideas.md
  • Modules/app-module/tests/CMakeLists.txt
  • Modules/app-module/tests/source/app_event_test.cpp
  • Modules/app-module/tests/source/app_manager_test.cpp
  • Modules/app-module/tests/source/main.cpp
  • Modules/crypt-module/tests/CMakeLists.txt
  • Modules/crypt-module/tests/source/crypt_test.cpp
  • Modules/crypt-module/tests/source/hash_test.cpp
  • Modules/crypt-module/tests/source/main.cpp
  • Modules/service-module/tests/CMakeLists.txt
  • Modules/service-module/tests/source/main.cpp
  • Modules/service-module/tests/source/service_paths_test.cpp
  • Modules/service-module/tests/source/service_test.cpp
  • Tactility/Tests/CMakeLists.txt
  • Tactility/Tests/Source/FileTest.cpp
  • Tactility/Tests/Source/Main.cpp
  • Tactility/Tests/Source/ObjectFileTest.cpp
  • Tactility/Tests/Source/StringTest.cpp
  • Tactility/Tests/Source/TestFile.h
  • Tactility/Tests/Source/UrlTest.cpp
  • TactilityFreeRtos/Tests/CMakeLists.txt
  • TactilityFreeRtos/Tests/Source/DispatcherTest.cpp
  • TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/LockTest.cpp
  • TactilityFreeRtos/Tests/Source/Main.cpp
  • TactilityFreeRtos/Tests/Source/MessageQueueTest.cpp
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp
  • TactilityFreeRtos/Tests/Source/PubSubTest.cpp
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp
  • TactilityFreeRtos/Tests/Source/SemaphoreTest.cpp
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp
  • TactilityKernel/tests/CMakeLists.txt
  • TactilityKernel/tests/source/bundle_test.cpp
  • TactilityKernel/tests/source/device_get_put_test.cpp
  • TactilityKernel/tests/source/device_listener_test.cpp
  • TactilityKernel/tests/source/device_test.cpp
  • TactilityKernel/tests/source/dispatcher_test.cpp
  • TactilityKernel/tests/source/driver_integration_test.cpp
  • TactilityKernel/tests/source/driver_test.cpp
  • TactilityKernel/tests/source/file_mutex_test.cpp
  • TactilityKernel/tests/source/file_system_test.cpp
  • TactilityKernel/tests/source/main.cpp
  • TactilityKernel/tests/source/memory_test.cpp
  • TactilityKernel/tests/source/module_test.cpp
  • TactilityKernel/tests/source/mutex_test.cpp
  • TactilityKernel/tests/source/paths_test.cpp
  • TactilityKernel/tests/source/preferences_test.cpp
  • TactilityKernel/tests/source/properties_file_test.cpp
  • TactilityKernel/tests/source/recursive_mutex_test.cpp
  • TactilityKernel/tests/source/system_event_test.cpp
  • TactilityKernel/tests/source/thread_test.cpp
  • TactilityKernel/tests/source/time_and_delay.cpp
  • TactilityKernel/tests/source/timer_test.cpp
  • Tests/CMakeLists.txt
  • Tests/Tactility/LICENSE-GPL-3.0.md
  • Tests/TactilityFreeRtos/LICENSE-Apache-2.0.md
  • Tests/TactilityKernel/LICENSE-Apache-2.0.md
  • Tests/crypt-module/LICENSE-Apache-2.0.md
  • Tests/service-module/LICENSE-Apache-2.0.md
💤 Files with no reviewable changes (6)
  • Tests/crypt-module/LICENSE-Apache-2.0.md
  • Tests/Tactility/LICENSE-GPL-3.0.md
  • Tests/TactilityFreeRtos/LICENSE-Apache-2.0.md
  • Documentation/ideas.md
  • Tests/service-module/LICENSE-Apache-2.0.md
  • Tests/TactilityKernel/LICENSE-Apache-2.0.md

Comment thread Modules/app-module/tests/CMakeLists.txt Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 16

🧹 Nitpick comments (5)
Modules/app-module/tests/source/app_manager_test.cpp (1)

393-396: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These two test cases depend on global state left by earlier test cases.

Line 396 requires that no app instance is Active. Line 441 has the same requirement. Both hold only when every earlier TEST_CASE stopped its instances and doctest runs the cases in declaration order. The checks fail if a developer runs a single case with --test-case=..., or randomizes with --order-by=rand.

Consider adding an explicit precondition helper that stops all remaining instances at the start of these cases, so they pass in isolation.

Modules/service-module/tests/source/service_test.cpp (1)

5-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This declaration is unused and its comment is stale.

No test case in this file calls service_instance_set_state, and no test exercises try_get/put gating. Remove the declaration, or add the test it was added for.

TactilityKernel/tests/source/time_and_delay.cpp (1)

5-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Loosen the upper bounds to avoid flaky timing tests.

The three tests require the measured delay to stay within one tick of the requested delay. The POSIX FreeRTOS simulator runs on a preemptible host scheduler, so a loaded CI machine can add several ticks of jitter. The lower bounds prove the delay APIs do not return early, which is the useful guarantee. Consider a larger tolerance for the upper bounds.

TactilityKernel/tests/source/preferences_test.cpp (1)

53-60: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Require a non-null Preferences before using it.

Most test cases call preferences_open and then pass the result straight into preferences_put_*. If preferences_open returns nullptr, the test crashes instead of reporting a failure. Line 45 uses CHECK_NE, which records the failure but still continues into the dereference. Use REQUIRE_NE after each preferences_open call, as lines 224 and 234 already do.

Proposed fix
     Preferences* preferences = preferences_open(TEST_PATH);
+    REQUIRE_NE(preferences, nullptr);
     preferences_put_bool(preferences, "flag", true);
TactilityKernel/tests/source/properties_file_test.cpp (1)

209-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Require a non-null PropertiesFile before using it.

Line 210 and line 219 pass the properties_file_open result straight into properties_file_set. If the open fails, the test crashes instead of reporting a failure. Add REQUIRE_NE(file, nullptr) after these properties_file_open calls, as line 188 already does.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c7ae0356-ff15-46f5-a6d3-620b6dcab6ea

📥 Commits

Reviewing files that changed from the base of the PR and between d6b1d15 and 0d86b01.

📒 Files selected for processing (60)
  • Documentation/ideas.md
  • Modules/app-module/tests/CMakeLists.txt
  • Modules/app-module/tests/source/app_event_test.cpp
  • Modules/app-module/tests/source/app_manager_test.cpp
  • Modules/app-module/tests/source/main.cpp
  • Modules/crypt-module/tests/CMakeLists.txt
  • Modules/crypt-module/tests/source/crypt_test.cpp
  • Modules/crypt-module/tests/source/hash_test.cpp
  • Modules/crypt-module/tests/source/main.cpp
  • Modules/service-module/tests/CMakeLists.txt
  • Modules/service-module/tests/source/main.cpp
  • Modules/service-module/tests/source/service_paths_test.cpp
  • Modules/service-module/tests/source/service_test.cpp
  • Tactility/Tests/CMakeLists.txt
  • Tactility/Tests/Source/FileTest.cpp
  • Tactility/Tests/Source/Main.cpp
  • Tactility/Tests/Source/ObjectFileTest.cpp
  • Tactility/Tests/Source/StringTest.cpp
  • Tactility/Tests/Source/TestFile.h
  • Tactility/Tests/Source/UrlTest.cpp
  • TactilityFreeRtos/Tests/CMakeLists.txt
  • TactilityFreeRtos/Tests/Source/DispatcherTest.cpp
  • TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/LockTest.cpp
  • TactilityFreeRtos/Tests/Source/Main.cpp
  • TactilityFreeRtos/Tests/Source/MessageQueueTest.cpp
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp
  • TactilityFreeRtos/Tests/Source/PubSubTest.cpp
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp
  • TactilityFreeRtos/Tests/Source/SemaphoreTest.cpp
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp
  • TactilityKernel/tests/CMakeLists.txt
  • TactilityKernel/tests/source/bundle_test.cpp
  • TactilityKernel/tests/source/device_get_put_test.cpp
  • TactilityKernel/tests/source/device_listener_test.cpp
  • TactilityKernel/tests/source/device_test.cpp
  • TactilityKernel/tests/source/dispatcher_test.cpp
  • TactilityKernel/tests/source/driver_integration_test.cpp
  • TactilityKernel/tests/source/driver_test.cpp
  • TactilityKernel/tests/source/file_mutex_test.cpp
  • TactilityKernel/tests/source/file_system_test.cpp
  • TactilityKernel/tests/source/main.cpp
  • TactilityKernel/tests/source/memory_test.cpp
  • TactilityKernel/tests/source/module_test.cpp
  • TactilityKernel/tests/source/mutex_test.cpp
  • TactilityKernel/tests/source/paths_test.cpp
  • TactilityKernel/tests/source/preferences_test.cpp
  • TactilityKernel/tests/source/properties_file_test.cpp
  • TactilityKernel/tests/source/recursive_mutex_test.cpp
  • TactilityKernel/tests/source/system_event_test.cpp
  • TactilityKernel/tests/source/thread_test.cpp
  • TactilityKernel/tests/source/time_and_delay.cpp
  • TactilityKernel/tests/source/timer_test.cpp
  • Tests/CMakeLists.txt
  • Tests/Tactility/LICENSE-GPL-3.0.md
  • Tests/TactilityFreeRtos/LICENSE-Apache-2.0.md
  • Tests/TactilityKernel/LICENSE-Apache-2.0.md
  • Tests/crypt-module/LICENSE-Apache-2.0.md
  • Tests/service-module/LICENSE-Apache-2.0.md
💤 Files with no reviewable changes (6)
  • Tests/crypt-module/LICENSE-Apache-2.0.md
  • Tests/Tactility/LICENSE-GPL-3.0.md
  • Tests/TactilityFreeRtos/LICENSE-Apache-2.0.md
  • Documentation/ideas.md
  • Tests/service-module/LICENSE-Apache-2.0.md
  • Tests/TactilityKernel/LICENSE-Apache-2.0.md
🛑 Comments failed to post (15)
Modules/crypt-module/tests/source/main.cpp (2)

46-46: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Both runners gate the task-creation check behind assert. When the test target is compiled with NDEBUG, assert expands to nothing, so a failed xTaskCreate is ignored, vTaskStartScheduler runs with no task, and main returns the initial data.result of 0. The suite then reports success without running any test. Modules/app-module/tests/source/main.cpp already uses an explicit check.

  • Modules/crypt-module/tests/source/main.cpp#L46-L46: replace assert(task_result == pdPASS) with if (task_result != pdPASS) { return 1; }.
  • Modules/service-module/tests/source/main.cpp#L46-L46: apply the same explicit check.
📍 Affects 2 files
  • Modules/crypt-module/tests/source/main.cpp#L46-L46 (this comment)
  • Modules/service-module/tests/source/main.cpp#L46-L46

53-59: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check which hosts/toolchains the module tests target, and whether __assert_fail is used elsewhere.
fd -t f 'CMakeLists.txt' Modules | xargs rg -n 'tests|APPLE|Darwin|CMAKE_SYSTEM_NAME' 
rg -n '__assert_fail|__assert_rtn|vAssertCalled' --glob '!**/build/**'
fd -t f -e yml -e yaml . .github 2>/dev/null | xargs rg -n 'runs-on'

Repository: TactilityProject/Tactility

Length of output: 164


🏁 Script executed:

#!/bin/bash
set +e
echo '--- tracked crypt-module test/build files ---'
git ls-files 'Modules/crypt-module/*' | sed -n '1,160p'
echo '--- candidate source files ---'
fd -t f -i 'main.cpp|CMakeLists.txt' Modules | sed -n '1,160p'
echo '--- symbol references ---'
rg -n --hidden --glob '!**/build/**' --glob '!**/.git/**' '__assert_fail|__assert_rtn|vAssertCalled' . | sed -n '1,160p'
echo '--- CI host declarations ---'
if [ -d .github ]; then
  rg -n --glob '*.yml' --glob '*.yaml' 'runs-on|macos|ubuntu|windows|Darwin|APPLE|CMAKE_SYSTEM_NAME' .github | sed -n '1,200p'
fi
echo '--- source context ---'
if [ -f Modules/crypt-module/tests/source/main.cpp ]; then
  cat -n Modules/crypt-module/tests/source/main.cpp | sed -n '1,100p'
fi
echo '--- nearby CMake files and relevant lines ---'
while IFS= read -r f; do
  echo "### $f"
  rg -n -C 4 'crypt-module|tests|add_executable|target_link|APPLE|Darwin|CMAKE_SYSTEM_NAME' "$f" | sed -n '1,180p'
done < <(fd -t f -i 'CMakeLists.txt' Modules/crypt-module Modules 2>/dev/null | sort -u | sed -n '1,80p')

Repository: TactilityProject/Tactility

Length of output: 4645


🏁 Script executed:

set +e
printf '%s\n' '--- source ---'
cat -n Modules/crypt-module/tests/source/main.cpp 2>&1 | sed -n '1,100p'
printf '%s\n' '--- nearby files ---'
find Modules/crypt-module -maxdepth 4 -type f \( -name 'CMakeLists.txt' -o -name '*.cmake' \) -print 2>&1
printf '%s\n' '--- symbols ---'
grep -RInE --exclude-dir=.git --exclude-dir=build '__assert_fail|__assert_rtn|vAssertCalled' Modules .github 2>&1 | sed -n '1,160p'
printf '%s\n' '--- hosts ---'
grep -RInE --include='*.yml' --include='*.yaml' 'runs-on|macos|ubuntu|windows' .github 2>&1 | sed -n '1,160p'

Repository: TactilityProject/Tactility

Length of output: 2974


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
root = Path(".")
source = root / "Modules/crypt-module/tests/source/main.cpp"
print("SOURCE_EXISTS", source.exists())
if source.exists():
    lines = source.read_text(errors="replace").splitlines()
    for n, line in enumerate(lines, 1):
        if "__assert_fail" in line or "vAssertCalled" in line or "assert" in line.lower():
            lo, hi = max(1, n - 4), min(len(lines), n + 4)
            print(f"SOURCE_CONTEXT {lo}-{hi}")
            for i in range(lo, hi + 1):
                print(f"{i}: {lines[i-1]}")
for p in sorted(root.rglob("*")):
    if not p.is_file() or ".git" in p.parts or "build" in p.parts:
        continue
    if p.name == "CMakeLists.txt" or p.suffix in {".yml", ".yaml"}:
        try:
            text = p.read_text(errors="replace")
        except Exception:
            continue
        hits = [i for i, line in enumerate(text.splitlines(), 1)
                if any(x in line for x in ("crypt-module", "runs-on", "APPLE",
                                           "Darwin", "CMAKE_SYSTEM_NAME",
                                           "add_executable", "target_link"))]
        if hits:
            print("FILE", p)
            for i in hits[:40]:
                lines = text.splitlines()
                lo, hi = max(1, i-2), min(len(lines), i+2)
                print(f"CONTEXT {lo}-{hi}")
                for j in range(lo, hi+1):
                    print(f"{j}: {lines[j-1]}")
PY

Repository: TactilityProject/Tactility

Length of output: 50382


🌐 Web query:

official documentation __assert_fail glibc __assert_rtn macOS libc assert

💡 Result:

Both assert_fail and assert_rtn are internal library functions used to implement the standard C assert macro [1][2][3]. They are not intended for direct use by application developers, but rather serve as the underlying handlers triggered when an assertion fails [4][3][5]. assert_fail (glibc) This function is the standard implementation for assertion failures in the GNU C Library (glibc), used on Linux and other systems following the Linux Standard Base [4][6]. When an expression passed to assert evaluates to false, the macro calls assert_fail to print a diagnostic message to stderr (including the file name, line number, and function name) and then terminates the program by calling abort [1][4][6][7]. Signature: void assert_fail(const char *assertion, const char *file, unsigned int line, const char *function); [4][6] assert_rtn (macOS/Darwin) This is the equivalent function used within the C library on Darwin-based systems, including macOS and iOS [8][9][10]. Like assert_fail, it is invoked by the assert macro when a check fails [3]. It serves the same primary purpose: printing diagnostic information and aborting the process [3][11]. The name assert_rtn is historically derived and specific to the BSD-derived C library used by Apple platforms [9][12]. Signature: void assert_rtn(const char *func, const char *file, uint line, const char *exp); [8][9][10] Key Differences The primary difference is the platform-specific implementation and the argument order [13]. While both perform identical roles, they are specific to their respective C library implementations (glibc vs. Darwin libc) [13]. Because these are internal "private" interfaces, their signatures or existence can change across library versions, and relying on them directly is generally discouraged [3][5]. Developers should always use the standard assert macro provided in <assert.h> to ensure portability [1][11].

Citations:


🏁 Script executed:

pwd
printf '%s\n' '--- matching tracked paths ---'
git ls-files | grep -Ei '(^|/)(main\.cpp|CMakeLists\.txt)$|crypt-module|\.github/.*\.ya?ml' | sed -n '1,240p'
printf '%s\n' '--- matching filesystem paths ---'
find . -type f \( -name 'main.cpp' -o -name 'CMakeLists.txt' -o -name '*.yml' -o -name '*.yaml' \) -print 2>/dev/null | grep -Ei 'crypt|github|module|main|cmake' | sed -n '1,240p'

Repository: TactilityProject/Tactility

Length of output: 17920


🏁 Script executed:

set -e
for f in \
  Modules/crypt-module/CMakeLists.txt \
  Modules/crypt-module/tests/CMakeLists.txt \
  CMakeLists.txt \
  Platforms/platform-posix/CMakeLists.txt \
  Devices/simulator/CMakeLists.txt \
  .github/workflows/build-simulator.yml \
  .github/actions/build-simulator/action.yml
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    cat -n "$f" | sed -n '1,240p'
  fi
done
printf '%s\n' '===== relevant references ====='
rg -n -C 5 'crypt-module|platform-posix|TactilityFreeRtos|Modules/crypt-module/tests|add_subdirectory\(.*tests|BUILD_TEST|ESP_PLATFORM|PLATFORM' \
  CMakeLists.txt Modules/crypt-module Platforms/platform-posix Devices/simulator TactilityFreeRtos .github/workflows/build-simulator.yml .github/actions/build-simulator/action.yml 2>/dev/null | sed -n '1,320p'

Repository: TactilityProject/Tactility

Length of output: 32833


🏁 Script executed:

set -e
echo '===== Modules/crypt-module/CMakeLists.txt ====='
cat -n Modules/crypt-module/CMakeLists.txt
echo '===== Modules/crypt-module/tests/CMakeLists.txt ====='
cat -n Modules/crypt-module/tests/CMakeLists.txt
echo '===== root CMake platform/test references ====='
rg -n -C 8 'add_subdirectory|ESP_PLATFORM|PLATFORM|BUILD_TEST|Tests|crypt-module|platform-posix' CMakeLists.txt | sed -n '1,260p'
echo '===== simulator CMake files ====='
cat -n Devices/simulator/CMakeLists.txt
echo '===== platform-posix CMake ====='
cat -n Platforms/platform-posix/CMakeLists.txt

Repository: TactilityProject/Tactility

Length of output: 4747


🏁 Script executed:

set -e
echo '===== Tests/CMakeLists.txt ====='
cat -n Tests/CMakeLists.txt | sed -n '1,260p'
echo '===== test workflow ====='
cat -n .github/workflows/tests.yml | sed -n '1,180p'
echo '===== all references to CryptModuleTests and test subdirectories ====='
rg -n -C 5 'CryptModuleTests|Modules/.*/tests|add_subdirectory\(.*tests|ctest|cmake --build' \
  CMakeLists.txt Tests Modules .github/workflows .github/actions \
  --glob '!Libraries/**' | sed -n '1,300p'

Repository: TactilityProject/Tactility

Length of output: 26815


Use a portable assertion failure handler.

The non-ESP build supports macOS, and Tests/CMakeLists.txt includes CryptModuleTests in build-tests. macOS does not provide __assert_fail, so this test cannot compile there. Replace it with fprintf(stderr, ...) followed by abort() using <cstdio> and <cstdlib>.

Tactility/Tests/Source/FileTest.cpp (1)

6-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the directories created by these tests.

These tests leave test1/test1 and /tmp/test2 after execution. Use unique test paths and remove the complete directory tree after each test. This prevents state leakage between runs.

Tactility/Tests/Source/Main.cpp (1)

49-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle xTaskCreate failure without assert.

In an NDEBUG build, Line 57 does nothing. If xTaskCreate fails, vTaskStartScheduler() can run without test_task and hang the test process. Return a failure result explicitly.

Proposed fix
-    assert(task_result == pdPASS);
+    if (task_result != pdPASS) {
+        return 1;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    BaseType_t task_result = xTaskCreate(
        test_task,
        "test_task",
        8192,
        &data,
        1,
        nullptr
    );
    if (task_result != pdPASS) {
        return 1;
    }

    vTaskStartScheduler();
Tactility/Tests/Source/ObjectFileTest.cpp (1)

24-31: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify the values in the multi-record read test.

The test verifies only read success and record count. It does not verify that either returned record matches the value written. Add value assertions after both readNext() calls.

Proposed fix
     CHECK_EQ(reader.hasNext(), true);
     CHECK_EQ(reader.readNext(&record_in), true);
+    CHECK_EQ(record_in.value, 0xAAAAAAAA);
     CHECK_EQ(reader.hasNext(), true);
     CHECK_EQ(reader.readNext(&record_in), true);
+    CHECK_EQ(record_in.value, 0xBBBBBBBB);
     CHECK_EQ(reader.hasNext(), false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    TestStruct record_in;
    ObjectFileReader reader = ObjectFileReader(TEMP_FILE, sizeof(TestStruct));
    CHECK_EQ(reader.open(), true);
    CHECK_EQ(reader.hasNext(), true);
    CHECK_EQ(reader.readNext(&record_in), true);
    CHECK_EQ(record_in.value, 0xAAAAAAAA);
    CHECK_EQ(reader.hasNext(), true);
    CHECK_EQ(reader.readNext(&record_in), true);
    CHECK_EQ(record_in.value, 0xBBBBBBBB);
    CHECK_EQ(reader.hasNext(), false);
Tactility/Tests/Source/UrlTest.cpp (1)

49-54: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename this test case to urlEncode.

This test calls network::urlEncode, but its name is urlDecode. Correct the name so test output identifies the API under test.

-TEST_CASE("urlDecode") {
+TEST_CASE("urlEncode") {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

TEST_CASE("urlEncode") {
    auto input = std::string("prefix!*'();:@&=+$,/?#[]<>%-.^_`{}|~ \\");
    auto expected = std::string("prefix%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%23%5B%5D%3C%3E%25-.%5E_%60%7B%7D%7C~+%5C");
    auto encoded = network::urlEncode(input);
    CHECK_EQ(encoded, expected);
}
TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp (1)

35-38: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Release every successful lock before test teardown.

The test at lines 35-38 leaves one recursive acquisition outstanding. The worker callbacks also return while they own their mutex. The next test can then run after FreeRTOS has deleted a held synchronization object.

  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp#L35-L38: Call mutex.unlock() twice before the test ends.
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp#L15-L18: Unlock mutex after lock() succeeds and before the callback returns.
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp#L15-L18: Unlock mutex after lock() succeeds and before the callback returns.
Proposed fix
-            mutex.lock(kernel::FREERTOS_MAX_TICKS);
+            if (mutex.lock(kernel::FREERTOS_MAX_TICKS)) {
+                mutex.unlock();
+            }
             return 0;
     CHECK_EQ(mutex.lock(0), true);
     CHECK_EQ(mutex.lock(0), true);
     mutex.unlock();
+    mutex.unlock();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        [&mutex] {
            if (mutex.lock(kernel::FREERTOS_MAX_TICKS)) {
                mutex.unlock();
            }
            return 0;
        }
    CHECK_EQ(mutex.lock(0), true);
    CHECK_EQ(mutex.lock(0), true);
    mutex.unlock();
    mutex.unlock();
}
📍 Affects 2 files
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp#L35-L38 (this comment)
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp#L15-L18
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp#L15-L18
TactilityFreeRtos/Tests/Source/ThreadTest.cpp (1)

25-41: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate test files ---'
git ls-files 'TactilityFreeRtos/Tests/Source/ThreadTest.cpp' \
  'TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp' \
  'TactilityFreeRtos/Tests/Source/TimerTest.cpp'
printf '%s\n' '--- relevant source and API references ---'
rg -n -C 4 'class (Thread|Timer|DispatcherThread)|void Thread::|Thread::|Timer::|delay(Ticks|Millis)|delayTicks|delayMillis|dispatch\\(' \
  TactilityFreeRtos TactilityCore 2>/dev/null | head -n 500
printf '%s\n' '--- test contents ---'
for f in TactilityFreeRtos/Tests/Source/ThreadTest.cpp \
         TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp \
         TactilityFreeRtos/Tests/Source/TimerTest.cpp; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat -n "$f"
  fi
done

Repository: TactilityProject/Tactility

Length of output: 5999


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- matching declarations and definitions ---'
rg -n -S 'class[[:space:]]+(Thread|Timer|DispatcherThread)|Thread::(start|join|getTaskHandle|getState)|Timer::(start|stop)|DispatcherThread::(dispatch|stop|start)|delayTicks|delayMillis' . \
  -g '!build' -g '!dist' -g '!node_modules' | head -n 800
printf '%s\n' '--- likely source files ---'
git ls-files | rg -i '(thread|timer|dispatcher|freertos|kernel).*\.(h|hpp|c|cc|cpp)$' | head -n 300
printf '%s\n' '--- synchronization usage in this module ---'
rg -n -S 'Semaphore|semaphore|notify|Notification|Mutex|mutex|binary|EventGroup|xQueue|portMAX_DELAY|take\\(|give\\(' TactilityFreeRtos . \
  -g '*.{h,hpp,c,cc,cpp}' | head -n 800

Repository: TactilityProject/Tactility

Length of output: 13816


🏁 Script executed:

#!/bin/bash
set -eu
for f in \
  TactilityFreeRtos/Include/Tactility/Thread.h \
  TactilityFreeRtos/Include/Tactility/Timer.h \
  TactilityFreeRtos/Include/Tactility/DispatcherThread.h \
  TactilityFreeRtos/Include/Tactility/Semaphore.h \
  TactilityFreeRtos/Include/Tactility/Mutex.h \
  TactilityFreeRtos/Include/Tactility/kernel/Kernel.h \
  TactilityFreeRtos/Tests/Source/SemaphoreTest.cpp \
  TactilityFreeRtos/Tests/Source/MutexTest.cpp; do
  echo "### $f"
  wc -l "$f"
  cat -n "$f"
done
printf '%s\n' '--- synchronization symbols ---'
rg -n -S 'Semaphore|semaphore|notify|Notification|Mutex|mutex|EventGroup|xQueue|portMAX_DELAY|take|give' \
  TactilityFreeRtos/Include TactilityFreeRtos/Tests/Source | head -n 800

Repository: TactilityProject/Tactility

Length of output: 48302


🏁 Script executed:

#!/bin/bash
set -eu
for f in \
  TactilityFreeRtos/Include/Tactility/Dispatcher.h \
  TactilityFreeRtos/Include/Tactility/EventGroup.h \
  TactilityFreeRtos/Include/Tactility/Lock.h \
  TactilityFreeRtos/Include/Tactility/freertoscompat/Timers.h \
  TactilityFreeRtos/Include/Tactility/freertoscompat/Task.h; do
  echo "### $f"
  cat -n "$f"
done
printf '%s\n' '--- timer configuration and test setup ---'
rg -n -S 'configTICK_RATE_HZ|configUSE_TIMERS|configTIMER_TASK|xTimer|TimerTest|DispatcherThreadTest' \
  TactilityFreeRtos Devices Platforms CMakeLists.txt .github 2>/dev/null | head -n 500
printf '%s\n' '--- analogous kernel concurrency tests ---'
for f in TactilityKernel/tests/source/dispatcher_test.cpp \
         TactilityKernel/tests/source/thread_test.cpp \
         TactilityKernel/tests/source/timer_test.cpp; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat -n "$f"
  fi
done

Repository: TactilityProject/Tactility

Length of output: 27222


🌐 Web query:

FreeRTOS xTimerStop callback already executing timer daemon task behavior xTimerDelete synchronization

💡 Result:

In FreeRTOS, software timer callbacks execute in the context of the timer daemon (service) task [1][2]. Because all timer API operations—including xTimerStop and xTimerDelete—send commands to the timer daemon task via a private command queue, synchronization relies on the sequential processing of this queue by the daemon task [3][2]. Behavior regarding currently executing callbacks: If you call xTimerStop or xTimerDelete while a timer's callback is already executing, the daemon task is already busy executing that callback [1]. Since the daemon task is single-threaded, it cannot process new commands (like STOP or DELETE) until the currently executing callback function returns [4][2]. Key considerations for synchronization: 1. Sequential Execution: Because the timer daemon task processes commands from its queue one by one, any call to xTimerStop or xTimerDelete made from a different task will be queued and processed only after the daemon task finishes its current task (i.e., completes the executing callback) [4][5][2]. 2. Blocking Risks: You must use a block time of 0 when calling xTimerDelete or xTimerStop from within a timer callback [4]. Blocking (using a non-zero block time) within a callback will cause the timer daemon task to stop processing the command queue, leading to a deadlock because no other timer commands can be processed and no other callbacks can execute [4][5]. 3. Race Conditions: Be aware that because timer operations are asynchronous (queued), calling xTimerStop does not guarantee that the timer's callback has not already been triggered or is currently executing [6]. If your application logic requires knowing whether the callback executed, you must implement state tracking (e.g., a flag or state machine) protected by critical sections or mutexes, rather than relying solely on the return value of timer API functions [6]. 4. Memory Safety: If you delete a timer and subsequently free its associated memory, ensure the timer is truly stopped or that your code does not attempt to reference the timer handle after the delete command has been processed by the daemon task [7][5]. If the timer daemon task has higher priority than your application tasks, and you do not use blocking calls in callbacks, the command queue is typically processed promptly [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

cases = {
    "TactilityFreeRtos/Tests/Source/ThreadTest.cpp": [
        (24, 42, "interrupted", "thread"),
        (44, 63, "interrupted", "thread"),
        (65, 86, "interrupted", "thread"),
    ],
    "TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp": [
        (17, 28, "counter", "dispatcher"),
    ],
    "TactilityFreeRtos/Tests/Source/TimerTest.cpp": [
        (6, 18, "counter", "timer"),
        (20, 30, "counter", "timer"),
        (32, 44, "counter", "timer"),
    ],
}

for filename, ranges in cases.items():
    text = Path(filename).read_text()
    lines = text.splitlines()
    print(f"### {filename}")
    for start, end, variable, kind in ranges:
        block = "\n".join(lines[start - 1:end])
        captured = bool(re.search(rf"\[&{variable}\]", block))
        writes = len(re.findall(rf"\b{re.escape(variable)}\s*(?:\+\+|=)", block))
        reads = len(re.findall(rf"\b(?:CHECK(?:_EQ|_GE|_NE)?\s*\([^;\n]*\b{re.escape(variable)}\b|while\s*\([^;\n]*\b{re.escape(variable)}\b)", block))
        waits = re.findall(r"\b(?:delayTicks|delayMillis|stop|join)\s*\(", block)
        atomic = bool(re.search(r"\bstd::atomic\b|`#include`\s*<atomic>", block))
        print({
            "lines": f"{start}-{end}",
            "kind": kind,
            "captured_by_callback": captured,
            "callback_writes": writes,
            "test_reads_or_stop_condition": reads,
            "scheduler_or_lifecycle_calls": waits,
            "atomic_used": atomic,
        })
PY

Repository: TactilityProject/Tactility

Length of output: 1802


Synchronize callback state and completion.

These tests share non-atomic variables between the test task and FreeRTOS tasks. delayTicks() and delayMillis() do not synchronize access. Timer::stop() also does not wait for a callback that is already running.

Use atomic state where appropriate and a semaphore or task notification for callback completion. Use bounded waits before assertions, stop(), and timer destruction. Replace the exact periodic count with a synchronized lower-bound assertion.

This applies to ThreadTest.cpp lines 25-41, 45-62, and 66-85; DispatcherThreadTest.cpp lines 20-26; and TimerTest.cpp lines 7-17, 20-29, and 33-43.

📍 Affects 3 files
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp#L25-L41 (this comment)
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp#L45-L62
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp#L66-L85
  • TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp#L20-L26
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp#L7-L17
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp#L20-L29
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp#L33-L43
TactilityKernel/tests/source/main.cpp (1)

47-57: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect test-target build definitions and build-type configuration.
fd -HI -t f '^(CMakeLists\.txt|.*\.cmake)$' . -x \
  rg -n -C 3 'NDEBUG|CMAKE_BUILD_TYPE|target_compile_definitions|add_compile_definitions'

Repository: TactilityProject/Tactility

Length of output: 16901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
fd -HI -t f 'main\.cpp|CMakeLists\.txt|.*\.cmake$' TactilityKernel . 2>/dev/null | head -200

printf '%s\n' '--- references to TactilityKernel test target/source ---'
rg -n -C 4 'TactilityKernel|tests/source/main\.cpp|xTaskCreate|vTaskStartScheduler' \
  --glob '!Libraries/**' --glob '!**/build/**' --glob '!**/dist/**' .

printf '%s\n' '--- direct NDEBUG and assertion configuration ---'
rg -n -C 3 '\bNDEBUG\b|CMAKE_CXX_FLAGS(_[A-Z]+)?|target_compile_options|add_compile_options|BUILD_TYPE' \
  TactilityKernel Buildscripts CMakeLists.txt Devices 2>/dev/null || true

Repository: TactilityProject/Tactility

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- TactilityKernel test files ---'
git ls-files 'TactilityKernel/Tests/*' 'TactilityKernel/tests/*' 'Tactility/Tests/Source/Main.cpp' 'Tactility/Tests/CMakeLists.txt' 'Tests/CMakeLists.txt'

printf '%s\n' '--- target build definitions ---'
for f in TactilityKernel/Tests/CMakeLists.txt TactilityKernel/tests/CMakeLists.txt Tactility/Tests/Source/Main.cpp Tactility/Tests/CMakeLists.txt Tests/CMakeLists.txt CMakeLists.txt; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat -n "$f"
  fi
done

printf '%s\n' '--- build configuration definitions ---'
rg -n -C 4 'CMAKE_BUILD_TYPE|CMAKE_CXX_FLAGS|NDEBUG|Release|Debug|Coverage|add_compile_options|target_compile_options' \
  CMakeLists.txt Tests TactilityKernel Tactility TactilityFreeRtos Devices/simulator Buildscripts 2>/dev/null || true

printf '%s\n' '--- POSIX FreeRTOS implementation references ---'
rg -n -C 5 'BaseType_t xTaskCreate|xTaskCreate\(|vTaskStartScheduler|taskYIELD|configMAX_PRIORITIES|configTOTAL_HEAP_SIZE|pvPortMalloc' \
  Libraries/FreeRTOS-Kernel TactilityFreeRtos Devices/simulator Platforms 2>/dev/null | head -500

Repository: TactilityProject/Tactility

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- task and scheduler implementation files ---'
rg -l 'BaseType_t\s+xTaskCreate|xTaskCreate\s*\(|void\s+vTaskStartScheduler|vTaskStartScheduler\s*\(' \
  Libraries/FreeRTOS-Kernel --glob '*.[ch]' --glob '*.[ch]pp' | sort

printf '%s\n' '--- exact implementation snippets ---'
rg -n -C 18 'BaseType_t\s+xTaskCreate|void\s+vTaskStartScheduler|xTaskGenericCreate|prvInitialiseNewTask' \
  Libraries/FreeRTOS-Kernel/portable Libraries/FreeRTOS-Kernel/tasks.c \
  --glob '*.[ch]' --glob '*.[ch]pp' | head -600

printf '%s\n' '--- test build invocations and configurations ---'
rg -n -C 4 'cmake|CMAKE_BUILD_TYPE|build-tests|TactilityKernelTests|ctest|Release|Debug|NDEBUG' \
  .github Buildscripts Documentation README.md Tests CMakeLists.txt \
  --glob '!**/doctest.h' --glob '!**/third_party/**' 2>/dev/null | head -800

Repository: TactilityProject/Tactility

Length of output: 41152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- scheduler implementation ---'
sed -n '1954,2045p' Libraries/FreeRTOS-Kernel/tasks.c

printf '%s\n' '--- xTaskCreate allocation implementation ---'
sed -n '719,813p' Libraries/FreeRTOS-Kernel/tasks.c

printf '%s\n' '--- POSIX/portable files and heap configuration ---'
fd -HI -t f . Libraries/FreeRTOS-Kernel | rg -i 'posix|heap|port|CMakeLists|FreeRTOSConfig'
rg -n -C 5 'FREERTOS_PORT|GCC_POSIX|heap_4|pvPortMalloc|configTOTAL_HEAP_SIZE|vPortGetHeapStats' \
  Libraries/FreeRTOS-Kernel Devices/simulator CMakeLists.txt TactilityKernel/tests .github \
  --glob '!**/doctest.h' 2>/dev/null | head -500

printf '%s\n' '--- focused source/config verifier ---'
python3 - <<'PY'
from pathlib import Path

paths = [
    Path("TactilityKernel/tests/source/main.cpp"),
    Path("TactilityKernel/tests/CMakeLists.txt"),
    Path("Tests/CMakeLists.txt"),
    Path("CMakeLists.txt"),
    Path(".github/workflows/tests.yml"),
    Path("Devices/simulator/Source/FreeRTOSConfig.h"),
    Path("Libraries/FreeRTOS-Kernel/tasks.c"),
]

for path in paths:
    text = path.read_text(errors="replace")
    print(f"{path}: NDEBUG occurrences={text.count('NDEBUG')}")

main = Path("TactilityKernel/tests/source/main.cpp").read_text()
assert "assert(task_result == pdPASS);" in main
assert "vTaskStartScheduler();" in main
assert main.index("assert(task_result == pdPASS);") < main.index("vTaskStartScheduler();")

tasks = Path("Libraries/FreeRTOS-Kernel/tasks.c").read_text()
needle = "xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;"
print(f"tasks.c: allocation-failure return occurrences={tasks.count(needle)}")
print(f"tasks.c: scheduler creates idle task={ 'xTaskCreateStatic' in tasks or 'xTaskCreate' in tasks }")
PY

Repository: TactilityProject/Tactility

Length of output: 50383


Handle task creation failure without assert.

If a build defines NDEBUG, assert(task_result == pdPASS) does not check the result. If xTaskCreate fails, return an error before calling vTaskStartScheduler; otherwise, TactilityKernelTests starts with only the idle task and can hang.

TactilityKernel/tests/source/module_test.cpp (1)

140-146: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the stale comment and stop the module before destructing it.

Line 141 states "Still fails as symbols are null", but line 142 asserts the resolution succeeds. The comment contradicts the assertion.

The cleanup also removes and destructs the module while it is still started. The "module_ensure_started" test at lines 190-192 calls module_stop first. Align both cleanup paths.

Proposed fix
     REQUIRE_EQ(module_start(&module), ERROR_NONE);
-    // Still fails as symbols are null
+    // Now resolvable: the module is added to the ledger and started.
     CHECK_EQ(module_resolve_symbol_global("symbol_test_function", &addr), true);
     // Cleanup
+    CHECK_EQ(module_stop(&module), ERROR_NONE);
     CHECK_EQ(module_remove(&module), ERROR_NONE);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    REQUIRE_EQ(module_start(&module), ERROR_NONE);
    // Now resolvable: the module is added to the ledger and started.
    CHECK_EQ(module_resolve_symbol_global("symbol_test_function", &addr), true);
    // Cleanup
    CHECK_EQ(module_stop(&module), ERROR_NONE);
    CHECK_EQ(module_remove(&module), ERROR_NONE);

    CHECK_EQ(module_destruct(&module), ERROR_NONE);
TactilityKernel/tests/source/mutex_test.cpp (1)

44-65: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The cross-task blocking tests destruct a mutex that the spawned task still holds. In both files the task locks the mutex, increments the counter, and calls vTaskDelete(nullptr) without unlocking. The test task then waits a fixed 2 ticks and destructs a stack-allocated mutex that the task still references.

  • TactilityKernel/tests/source/mutex_test.cpp#L44-L65: call mutex_unlock(mutex_ptr) in the task body before vTaskDelete(nullptr).
  • TactilityKernel/tests/source/recursive_mutex_test.cpp#L64-L85: call recursive_mutex_unlock(mutex_ptr) in the task body before vTaskDelete(nullptr).
📍 Affects 2 files
  • TactilityKernel/tests/source/mutex_test.cpp#L44-L65 (this comment)
  • TactilityKernel/tests/source/recursive_mutex_test.cpp#L64-L85
TactilityKernel/tests/source/preferences_test.cpp (1)

191-191: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct this test case name.

The name says "on an already-closed value", but the test writes to a reopened Preferences instance. Rename it to describe the reopen-then-update behavior it verifies.

TactilityKernel/tests/source/properties_file_test.cpp (1)

30-34: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Duplicated write_raw helper skips the fopen null check in both test files. Both files define the same helper, and both pass an unchecked FILE* to std::fputs and std::fclose, so an open failure crashes the test process instead of reporting a failure.

  • TactilityKernel/tests/source/properties_file_test.cpp#L30-L34: add REQUIRE(file != nullptr); after std::fopen.
  • TactilityKernel/tests/source/preferences_test.cpp#L34-L38: add the same REQUIRE(file != nullptr); after std::fopen.

ScratchFile, file_exists, and write_raw are duplicated across both files. Consider moving them into a shared test helper header under TactilityKernel/tests/source/.

🧰 Tools
🪛 Cppcheck (2.21.0)

[warning] 32-32: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)

📍 Affects 2 files
  • TactilityKernel/tests/source/properties_file_test.cpp#L30-L34 (this comment)
  • TactilityKernel/tests/source/preferences_test.cpp#L34-L38

Source: Linters/SAST tools

TactilityKernel/tests/source/system_event_test.cpp (1)

253-253: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use a larger join timeout for the emitter threads.

Line 253 joins the emitter thread with a 2-tick timeout. Lines 329 and 360 do the same. The awaiting task resumes as soon as the emitter calls system_event_emit, so the emitter may still be inside its exit path when thread_join starts. Lines 402 and 435 already use pdMS_TO_TickS(2000)-scale timeouts. Align these three joins with that value to remove the flakiness.

Proposed fix
-    CHECK_EQ(thread_join(thread, 2, 1), ERROR_NONE);
+    CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), pdMS_TO_TICKS(1)), ERROR_NONE);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), pdMS_TO_TICKS(1)), ERROR_NONE);
TactilityKernel/tests/source/thread_test.cpp (1)

100-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does doctest report an error for duplicated TEST_CASE names?

💡 Result:

No, the doctest C++ testing framework does not report an error for duplicated TEST_CASE names [1][2][3]. The framework allows test cases to share the same name without restriction [2][3]. When multiple test cases have the same name, doctest will execute each of them as distinct tests [1]. Test cases are uniquely identified by the framework based on their registration (internal metadata including file and line number), not by the string literal used as the name [4][3]. Note that this behavior is specific to the C++ doctest library [2]. It is distinct from the Python doctest module (found in the standard library), which serves a different purpose and operates under different rules [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
file='TactilityKernel/tests/source/thread_test.cpp'
printf '%s\n' '--- target tests ---'
sed -n '1,125p' "$file"
printf '%s\n' '--- doctest references and version context ---'
rg -n 'doctest|TEST_CASE|thread return code|thread id should only' \
  TactilityKernel/tests "$file" CMakeLists.txt '**/CMakeLists.txt' 2>/dev/null | head -200

Repository: TactilityProject/Tactility

Length of output: 22052


Rename the duplicate test case.

The test at line 100 verifies the return code, not the thread ID. Use a distinct name so --test-case can select it independently.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f3f89ac-7d15-45ad-84ce-56d4816b61c6

📥 Commits

Reviewing files that changed from the base of the PR and between 0d86b01 and 966035b.

⛔ Files ignored due to path filters (1)
  • .claude/rules.zip is excluded by !**/*.zip
📒 Files selected for processing (27)
  • .claude/rules/tests.md
  • Modules/app-module/tests/CMakeLists.txt
  • Modules/crypt-module/tests/CMakeLists.txt
  • Modules/crypt-module/tests/source/main.cpp
  • Modules/service-module/tests/CMakeLists.txt
  • Modules/service-module/tests/source/main.cpp
  • Tactility/Source/file/File.cpp
  • Tactility/Tests/CMakeLists.txt
  • Tactility/Tests/Source/FileTest.cpp
  • Tactility/Tests/Source/Main.cpp
  • Tactility/Tests/Source/ObjectFileTest.cpp
  • Tactility/Tests/Source/UrlTest.cpp
  • TactilityFreeRtos/Tests/CMakeLists.txt
  • TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp
  • TactilityKernel/tests/CMakeLists.txt
  • TactilityKernel/tests/source/main.cpp
  • TactilityKernel/tests/source/module_test.cpp
  • TactilityKernel/tests/source/mutex_test.cpp
  • TactilityKernel/tests/source/preferences_test.cpp
  • TactilityKernel/tests/source/properties_file_test.cpp
  • TactilityKernel/tests/source/recursive_mutex_test.cpp
  • TactilityKernel/tests/source/system_event_test.cpp
  • TactilityKernel/tests/source/thread_test.cpp
🚧 Files skipped from review as they are similar to previous changes (22)
  • TactilityFreeRtos/Tests/Source/DispatcherThreadTest.cpp
  • TactilityFreeRtos/Tests/Source/MutexTest.cpp
  • Modules/crypt-module/tests/CMakeLists.txt
  • TactilityKernel/tests/source/mutex_test.cpp
  • TactilityFreeRtos/Tests/Source/RecursiveMutexTest.cpp
  • Modules/app-module/tests/CMakeLists.txt
  • Tactility/Tests/Source/FileTest.cpp
  • Tactility/Tests/CMakeLists.txt
  • TactilityKernel/tests/source/main.cpp
  • Modules/service-module/tests/CMakeLists.txt
  • TactilityKernel/tests/source/recursive_mutex_test.cpp
  • TactilityFreeRtos/Tests/CMakeLists.txt
  • TactilityKernel/tests/CMakeLists.txt
  • TactilityKernel/tests/source/module_test.cpp
  • Tactility/Tests/Source/UrlTest.cpp
  • Tactility/Tests/Source/ObjectFileTest.cpp
  • TactilityKernel/tests/source/properties_file_test.cpp
  • TactilityKernel/tests/source/thread_test.cpp
  • TactilityKernel/tests/source/preferences_test.cpp
  • Modules/service-module/tests/source/main.cpp
  • TactilityFreeRtos/Tests/Source/ThreadTest.cpp
  • Tactility/Tests/Source/Main.cpp

Comment thread Modules/crypt-module/tests/source/main.cpp Outdated
Comment thread Tactility/Source/file/File.cpp
Comment thread TactilityFreeRtos/Tests/Source/TimerTest.cpp Outdated
Comment thread TactilityFreeRtos/Tests/Source/TimerTest.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 03f76d92-f4d2-41b4-942e-0baf86ba169e

📥 Commits

Reviewing files that changed from the base of the PR and between 966035b and cffe58e.

📒 Files selected for processing (3)
  • Modules/crypt-module/tests/source/main.cpp
  • Tactility/Source/file/File.cpp
  • TactilityFreeRtos/Tests/Source/TimerTest.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • Modules/crypt-module/tests/source/main.cpp
  • Tactility/Source/file/File.cpp

Comment on lines +12 to +18
void waitForTimerServiceIdle(Timer& timer) {
Semaphore done(1, 0);
auto markDone = [](void* context, uint32_t) {
static_cast<Semaphore*>(context)->release();
};
REQUIRE(timer.setPendingCallback(markDone, &done, 0, pdMS_TO_TICKS(2000)));
REQUIRE(done.acquire(pdMS_TO_TICKS(2000)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'REQUIRE|DOCTEST_CONFIG_NO_EXCEPTIONS|TestFailureException' \
  TactilityFreeRtos/Tests TactilityFreeRtos Libraries 2>/dev/null || true

rg -n -C 6 \
  'waitForTimerServiceIdle|setPendingCallback|xTimerPendFunctionCall' \
  TactilityFreeRtos/Tests/Source/TimerTest.cpp \
  TactilityFreeRtos/Include/Tactility/Timer.h

Repository: TactilityProject/Tactility

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test helper and surrounding tests ---'
sed -n '1,240p' TactilityFreeRtos/Tests/Source/TimerTest.cpp

printf '%s\n' '--- timer callback API and implementation references ---'
rg -n -C 8 'setPendingCallback|xTimerPendFunctionCall|TimerCallback|Semaphore' \
  TactilityFreeRtos/Include TactilityFreeRtos/Source TactilityFreeRtos/Tests/Source \
  -g '*.h' -g '*.hpp' -g '*.cpp' -g '*.c' 2>/dev/null | head -n 1200

printf '%s\n' '--- doctest configuration and assertion behavior references ---'
rg -n -C 8 'DOCTEST_CONFIG_NO_EXCEPTIONS|TestFailureException|REQUIRE\(' \
  TactilityFreeRtos/Tests TactilityFreeRtos -g '*.h' -g '*.hpp' -g '*.cpp' -g '*.c' 2>/dev/null | head -n 1200

Repository: TactilityProject/Tactility

Length of output: 35940


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- doctest configuration ---'
git ls-files | rg -i '(^|/)(doctest|test|main|CMakeLists|meson|platformio|idf_component).*' | head -n 500
rg -n -C 5 'DOCTEST_CONFIG_NO_EXCEPTIONS|DOCTEST_CONFIG_IMPLEMENT|TestFailureException|TEST_CASE|REQUIRE\(' \
  --glob '!Libraries/SDL/**' --glob '!Libraries/lvgl/**' --glob '!Libraries/mbedtls/**' \
  . 2>/dev/null | head -n 1600

printf '%s\n' '--- exact Timer API and callback declarations ---'
sed -n '1,155p' TactilityFreeRtos/Include/Tactility/Timer.h

printf '%s\n' '--- timer service and pending-call implementation references ---'
rg -n -C 12 'xTimerPendFunctionCall|timer service|TimerPendedFunction|pend.*function|Pended' \
  TactilityFreeRtos Libraries -g '*.c' -g '*.cpp' -g '*.h' -g '*.hpp' 2>/dev/null | head -n 1600

Repository: TactilityProject/Tactility

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- doctest files and configuration ---'
git ls-files | rg -i 'doctest|test[_-]?main|main[_-]?test|tests?/.*main' | head -n 300
rg -n 'DOCTEST_CONFIG_NO_EXCEPTIONS|TestFailureException' \
  TactilityFreeRtos TactilityKernel cmake CMakeLists.txt 2>/dev/null | head -n 300 || true

printf '%s\n' '--- Timer.h relevant declarations ---'
nl -ba TactilityFreeRtos/Include/Tactility/Timer.h | sed -n '20,145p'

printf '%s\n' '--- pending callback implementations in project files ---'
rg -n 'xTimerPendFunctionCall|setPendingCallback' \
  TactilityFreeRtos TactilityKernel -g '*.c' -g '*.cpp' -g '*.h' -g '*.hpp' 2>/dev/null | head -n 300

printf '%s\n' '--- test target definitions ---'
rg -n -C 6 'TimerTest.cpp|doctest|DOCTEST' \
  TactilityFreeRtos -g 'CMakeLists.txt' -g '*.cmake' -g '*.cpp' -g '*.h' 2>/dev/null | head -n 600

Repository: TactilityProject/Tactility

Length of output: 635


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- doctest configuration ---'
rg -n -C 8 'DOCTEST_CONFIG_NO_EXCEPTIONS|TestFailureException|DOCTEST_CONFIG_IMPLEMENT' \
  TactilityFreeRtos/Tests/Source/Main.cpp \
  TactilityFreeRtos/Tests \
  Tests/Doctest/Include/doctest.h 2>/dev/null | head -n 500 || true

printf '%s\n' '--- Timer.h relevant declarations ---'
cat -n TactilityFreeRtos/Include/Tactility/Timer.h | sed -n '20,145p'

printf '%s\n' '--- pending callback implementations in project files ---'
rg -n -C 10 'xTimerPendFunctionCall|setPendingCallback' \
  TactilityFreeRtos TactilityKernel -g '*.c' -g '*.cpp' -g '*.h' -g '*.hpp' 2>/dev/null | head -n 500

printf '%s\n' '--- test target definitions ---'
rg -n -C 6 'TimerTest.cpp|doctest|DOCTEST' \
  TactilityFreeRtos -g 'CMakeLists.txt' -g '*.cmake' -g '*.cpp' -g '*.h' 2>/dev/null | head -n 500

Repository: TactilityProject/Tactility

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

timer_test = Path("TactilityFreeRtos/Tests/Source/TimerTest.cpp").read_text()
timer_h = Path("TactilityFreeRtos/Include/Tactility/Timer.h").read_text()
semaphore_h = Path("TactilityFreeRtos/Include/Tactility/Semaphore.h").read_text()
doctest_h = Path("Tests/Doctest/Include/doctest.h").read_text()

checks = {
    "automatic completion context": re.search(
        r"void waitForTimerServiceIdle\(Timer& timer\)\s*\{.*?Semaphore done\(1,\s*0\);",
        timer_test, re.S,
    ),
    "stack context passed to asynchronous callback": re.search(
        r"setPendingCallback\(markDone,\s*&done,", timer_test,
    ),
    "callback dereferences context": re.search(
        r"static_cast<Semaphore\*>(context\)->release\(\);", timer_test,
    ),
    "pending callback uses timer service queue": re.search(
        r"xTimerPendFunctionCall\(newCallback,\s*callbackContext,\s*callbackArg,\s*timeout\)",
        timer_h,
    ),
    "Semaphore owns a deletable handle": re.search(
        r"std::unique_ptr<.*SemaphoreHandleDeleter>\s+handle;", semaphore_h,
    ),
    "Semaphore destructor is present": re.search(
        r"~Semaphore\(\)\s+override\s*\{", semaphore_h,
    ),
    "doctest throws on failed assertion when exceptions are enabled": re.search(
        r"void throwException\(\)\s*\{\s*g_cs->shouldLogCurrentException\s*=\s*false;\s*throw TestFailureException\(\);",
        doctest_h, re.S,
    ),
    "doctest catches the failure outside the test body": re.search(
        r"catch\(const TestFailureException&\)\s*\{",
        doctest_h,
    ),
}

for name, match in checks.items():
    print(f"{name}: {'present' if match else 'missing'}")

if not all(checks.values()):
    raise SystemExit("source invariant check failed")

print("Conclusion: a failed second REQUIRE can unwind after enqueueing the callback, while the callback still holds &done.")
PY

Repository: TactilityProject/Tactility

Length of output: 988


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

timer_test = Path("TactilityFreeRtos/Tests/Source/TimerTest.cpp").read_text()
timer_h = Path("TactilityFreeRtos/Include/Tactility/Timer.h").read_text()
semaphore_h = Path("TactilityFreeRtos/Include/Tactility/Semaphore.h").read_text()
doctest_h = Path("Tests/Doctest/Include/doctest.h").read_text()

checks = {
    "automatic completion context":
        "Semaphore done(1, 0);" in timer_test,
    "stack context passed to asynchronous callback":
        "setPendingCallback(markDone, &done," in timer_test,
    "callback dereferences context":
        "static_cast<Semaphore*>(context)->release();" in timer_test,
    "pending callback uses timer service queue":
        "xTimerPendFunctionCall(newCallback, callbackContext, callbackArg, timeout)" in timer_h,
    "Semaphore owns a deletable handle":
        "SemaphoreHandleDeleter> handle;" in semaphore_h,
    "Semaphore destructor is present":
        "~Semaphore() override" in semaphore_h,
    "doctest throws on failed assertion":
        "throw TestFailureException();" in doctest_h,
    "doctest catches the failure outside the test body":
        "catch(const TestFailureException&)" in doctest_h,
}

for name, present in checks.items():
    print(f"{name}: {'present' if present else 'missing'}")

if not all(checks.values()):
    raise SystemExit("source invariant check failed")

print("Conclusion: the helper passes a stack Semaphore to a timer-service callback, and a failed REQUIRE can unwind before that callback finishes.")
PY

Repository: TactilityProject/Tactility

Length of output: 674


Keep the completion context alive until markDone returns.

If done.acquire(...) times out, REQUIRE unwinds the helper while the timer-service callback can still execute. markDone then dereferences the destroyed stack Semaphore, whose owned FreeRTOS handle has also been deleted. Use a completion context that remains valid through the callback, including the timeout path.

Source: MCP tools

Comment on lines +28 to +31
const auto first_run_count = counter.load();

CHECK_EQ(timer->start(), true);
kernel::delayTicks(10);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the first periodic run produced a callback.

first_run_count is only used as a baseline. If the first run produces zero callbacks and the second run produces one, CHECK_GT(counter.load(), first_run_count) passes. The test then does not prove that the timer worked before restart.

Add a positive assertion before the second start():

🧪 Proposed assertion
     const auto first_run_count = counter.load();
+    CHECK_GT(first_run_count, 0);
 
     CHECK_EQ(timer->start(), true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const auto first_run_count = counter.load();
CHECK_EQ(timer->start(), true);
kernel::delayTicks(10);
const auto first_run_count = counter.load();
CHECK_GT(first_run_count, 0);
CHECK_EQ(timer->start(), true);
kernel::delayTicks(10);

CHECK_EQ(timer->start(), true);
kernel::delayTicks(10);
CHECK_EQ(timer->stop(), true);
waitForTimerServiceIdle(*timer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  'restarting TimerType::Once|timer->stop\(\)|timer->start\(\)|waitForTimerServiceIdle' \
  TactilityFreeRtos/Tests/Source/TimerTest.cpp \
  TactilityFreeRtos/Include/Tactility/Timer.h

Repository: TactilityProject/Tactility

Length of output: 4943


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- TimerTest.cpp ---'
cat -n TactilityFreeRtos/Tests/Source/TimerTest.cpp | sed -n '1,90p'

printf '%s\n' '--- Timer.h ---'
cat -n TactilityFreeRtos/Include/Tactility/Timer.h | sed -n '1,220p'

printf '%s\n' '--- timer implementation and test references ---'
rg -n -C 5 'Timer::(start|stop)|xTimer(Start|Stop)|timer service|waitForTimerServiceIdle' \
  TactilityFreeRtos TactilityFreeRtos/Tests

Repository: TactilityProject/Tactility

Length of output: 19063


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- timer API implementations and compatibility layers ---'
rg -n -C 6 \
  'xTimerStop|xTimerStart|xTimerPendFunctionCall|TimerService|timer command|tmrCOMMAND_STOP|tmrCOMMAND_START' \
  . -g '!build' -g '!dist' -g '!node_modules' | sed -n '1,260p'

printf '%s\n' '--- relevant repository files ---'
git ls-files | rg '(^|/)(Timer|Timers|FreeRTOS|freertos|TimerTest)' | sed -n '1,160p'

printf '%s\n' '--- deterministic queue-order model ---'
python3 - <<'PY'
from collections import deque

def process(commands, expiry_pending):
    queue = deque(commands)
    callbacks = 0
    active = True
    while queue:
        command = queue.popleft()
        if command == "expiry":
            if active:
                callbacks += 1
                active = False
        elif command == "stop":
            active = False
        elif command == "start":
            active = True
        elif command == "marker":
            return callbacks, active, list(queue)
    return callbacks, active, []

for first_expiry in (False, True):
    without_wait = ["stop", "start", "expiry" if first_expiry else "marker", "stop", "marker"]
    with_wait = ["stop", "marker", "start", "expiry" if first_expiry else "marker", "stop", "marker"]
    print({
        "expiry_already_queued": first_expiry,
        "without_wait": process(without_wait, first_expiry),
        "with_wait": process(with_wait, first_expiry),
    })
PY

Repository: TactilityProject/Tactility

Length of output: 24876


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- FreeRTOS timer command implementation ---'
fd -i 'timers.c' Libraries TactilityFreeRtos TactilityKernel Platforms Devices
fd -i '*timer*.c' Libraries/FreeRTOS-Kernel | sed -n '1,80p'

printf '%s\n' '--- timer command and callback processing ---'
rg -n -C 8 \
  'xTimerGenericCommand|prvProcessReceivedCommands|prvProcessExpiredTimer|tmrCOMMAND_EXECUTE_CALLBACK|xTimerPendFunctionCall' \
  Libraries/FreeRTOS-Kernel -g '*.c' -g '*.h' | sed -n '1,360p'

Repository: TactilityProject/Tactility

Length of output: 534


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- timer command and callback processing ---'
rg -n -C 10 \
  'xTimerGenericCommand|prvProcessReceivedCommands|prvProcessExpiredTimer|tmrCOMMAND_EXECUTE_CALLBACK|xTimerPendFunctionCall' \
  Libraries/FreeRTOS-Kernel/timers.c Libraries/FreeRTOS-Kernel/include/timers.h | sed -n '1,420p'

Repository: TactilityProject/Tactility

Length of output: 34799


Synchronize the first one-shot run before restarting.

Timer::stop() only queues a command and does not wait for timer-service processing. Add waitForTimerServiceIdle(*timer) after the first stop() and before the second start(). Otherwise, the first callback can be suppressed, making the exact-two assertion scheduling-dependent.

Source: MCP tools

@KenVanHoeylandt
KenVanHoeylandt merged commit f943c4d into main Aug 13, 2026
63 checks passed
@KenVanHoeylandt
KenVanHoeylandt deleted the move-tests branch August 13, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant