-
-
Notifications
You must be signed in to change notification settings - Fork 94
Move tests to relevant subprojects #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
092697c
0d86b01
6699cd7
b511e5b
1fc287b
989190c
966035b
cffe58e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Tests for projects should be placed in the `Tests/` or `tests/` subfolder of that project. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||
| #include "doctest.h" | ||||||||||||||||||||
| #include <Tactility/Semaphore.h> | ||||||||||||||||||||
| #include <Tactility/Timer.h> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #include <atomic> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using namespace tt; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // stop() only enqueues a command; it doesn't wait for the timer service task to process it or | ||||||||||||||||||||
| // finish an in-flight callback. Queuing a pending callback on that same queue and waiting for it | ||||||||||||||||||||
| // does, since the service task processes its queue in order. | ||||||||||||||||||||
| 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))); | ||||||||||||||||||||
|
Comment on lines
+12
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.hRepository: 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 1200Repository: 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 1600Repository: 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 600Repository: 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 500Repository: 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.")
PYRepository: 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.")
PYRepository: TactilityProject/Tactility Length of output: 674 Keep the completion context alive until If Source: MCP tools |
||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| TEST_CASE("TimerType::Periodic timers can be stopped and restarted") { | ||||||||||||||||||||
| std::atomic<int> counter{0}; | ||||||||||||||||||||
| auto* timer = new Timer(Timer::Type::Periodic, 1, [&counter] { counter++; }); | ||||||||||||||||||||
| CHECK_EQ(timer->start(), true); | ||||||||||||||||||||
| kernel::delayTicks(10); | ||||||||||||||||||||
| CHECK_EQ(timer->stop(), true); | ||||||||||||||||||||
| waitForTimerServiceIdle(*timer); | ||||||||||||||||||||
| const auto first_run_count = counter.load(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| CHECK_EQ(timer->start(), true); | ||||||||||||||||||||
| kernel::delayTicks(10); | ||||||||||||||||||||
|
Comment on lines
+28
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Add a positive assertion before the second 🧪 Proposed assertion const auto first_run_count = counter.load();
+ CHECK_GT(first_run_count, 0);
CHECK_EQ(timer->start(), true);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
| CHECK_EQ(timer->stop(), true); | ||||||||||||||||||||
| waitForTimerServiceIdle(*timer); | ||||||||||||||||||||
| delete timer; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| CHECK_GT(counter.load(), first_run_count); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| TEST_CASE("TimerType::Periodic calls the callback periodically") { | ||||||||||||||||||||
| int ticks_to_run = 10; | ||||||||||||||||||||
| std::atomic<int> counter{0}; | ||||||||||||||||||||
| auto* timer = new Timer(Timer::Type::Periodic, 1, [&counter] { counter++; }); | ||||||||||||||||||||
| CHECK_EQ(timer->start(), true); | ||||||||||||||||||||
| kernel::delayTicks(ticks_to_run); | ||||||||||||||||||||
| CHECK_EQ(timer->stop(), true); | ||||||||||||||||||||
| waitForTimerServiceIdle(*timer); | ||||||||||||||||||||
| delete timer; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Exact count isn't guaranteed (scheduling slop around start()/stop()), so this only checks | ||||||||||||||||||||
| // that the callback fired repeatedly, not an exact tick-for-tick match. | ||||||||||||||||||||
| CHECK_GE(counter.load(), ticks_to_run / 2); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| TEST_CASE("restarting TimerType::Once timers calls the callback again") { | ||||||||||||||||||||
| std::atomic<int> counter{0}; | ||||||||||||||||||||
| auto* timer = new Timer(Timer::Type::Once, 1, [&counter] { counter++; }); | ||||||||||||||||||||
| CHECK_EQ(timer->start(), true); | ||||||||||||||||||||
| kernel::delayTicks(10); | ||||||||||||||||||||
| CHECK_EQ(timer->stop(), true); | ||||||||||||||||||||
| CHECK_EQ(timer->start(), true); | ||||||||||||||||||||
| kernel::delayTicks(10); | ||||||||||||||||||||
| CHECK_EQ(timer->stop(), true); | ||||||||||||||||||||
| waitForTimerServiceIdle(*timer); | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.hRepository: 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/TestsRepository: 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),
})
PYRepository: 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.
Source: MCP tools |
||||||||||||||||||||
| delete timer; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| CHECK_EQ(counter.load(), 2); | ||||||||||||||||||||
| } | ||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.