POST coverage cluster 2/4: Q48.16 math primitives (proof-covered, previously untested)
New module (q48_words_test.c, Module 25 -- matches word_registry.c's own existing numbering for this file's registration) covers all 23 words in q48_words.c: no test file existed for this file at all before. Standard WordTestSuite/TestCase tabular format, unlike ACL's hand-rolled style -- these are pure stateless functions, a natural fit. 28 TestCase entries; values built via Q.FROM-INT/Q.1/Q.0, read back via Q.TO-INT for readable log output. Verified q48_16.h's q48_to_u64() sign-extends through a signed int64_t intermediate before writing the Q.NEG/Q.ABS tests, rather than assuming negative round-trip works. Boot-verified: zero build warnings, all 23 words pass individually, FINAL TEST SUMMARY 1003->1031 total / 965->993 passed (+28 exactly), 0 failed, 0 errors. Noted (pre-existing, not fixed): print_module_summary() is called with hardcoded (name,0,0,0,0) across every WordTestSuite module in the tree, including this new one -- decorative, always zero; the real counts live in each word's own per-suite line and the global summary. Cluster 3 of 4 in the POST-coverage sequence (code sweeps -> HOL green -> POST coverage, one proof-covered cluster at a time). Two clusters left: inference-engine accessors, physics freeze/diagnostic. Full writeup in FABRIC-2.md Section J. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b2918fd0b2
commit
7100523656
+33
@@ -1473,3 +1473,36 @@ evidence the bug was real and caught, not hidden), `logs/20260818-231403/` the f
|
||||
Remaining clusters (Q48.16, inference-engine, physics freeze/diagnostic) not yet started —
|
||||
next up, one at a time, same process: read the C implementation, write tests matching that
|
||||
file's existing style, boot and verify before committing.
|
||||
|
||||
**Cluster 2, Q48.16 math primitives — done.** No test file for `q48_words.c` existed at all
|
||||
(confirmed by absence, not by reading one and finding it thin) — all 23 registered words
|
||||
(`Q.+`, `Q.-`, `Q.*`, `Q./`, `Q.ABS`, `Q.NEG`, `Q.LOG`, `Q.EXP`, `Q.SQRT`, `Q.SIN`, `Q.COS`,
|
||||
`Q.FROM-INT`, `Q.TO-INT`, `Q.1`, `Q.0`, `Q.SCALE`, `Q.=`, `Q.<`, `Q.>`, `Q.0=`, `Q.MAX`,
|
||||
`Q.MIN`, `Q.PRINT`) had zero POST coverage, not just the 17 `proof/COVERAGE.md` marks
|
||||
proof-covered — covering the whole file was simpler and more valuable than partitioning by
|
||||
proof status. Unlike ACL's hand-rolled style, these are pure stateless functions — a natural
|
||||
fit for the standard `WordTestSuite`/`TestCase` tabular format the majority of the tree
|
||||
already uses, so a new file (`q48_words_test.c`, Module 25 — matching `word_registry.c`'s own
|
||||
existing "Module 25: Q48.16 Fixed-Point Math" numbering, not a coincidence, both count the
|
||||
same registration) was added rather than extending an existing one. 28 `TestCase` entries
|
||||
total (some words got two — a true/false pair for comparisons, a divide-by-zero edge case for
|
||||
`Q./`). New words compose values via `Q.FROM-INT`/`Q.1`/`Q.0` and read them back via
|
||||
`Q.TO-INT` so the log shows plain readable integers.
|
||||
|
||||
One genuine implementation subtlety checked before writing tests, not assumed: whether
|
||||
`Q.TO-INT` round-trips a *negative* Q48.16 value correctly (needed for the `Q.NEG`/`Q.ABS`
|
||||
tests). `q48_16.h`'s own `q48_to_u64()` comment confirms it shifts through a signed `int64_t`
|
||||
intermediate specifically to sign-extend correctly — verified in the header before trusting
|
||||
it, not discovered by a failing test.
|
||||
|
||||
Verified: zero build warnings, boot-tested, all 23 words individually pass (each printing its
|
||||
own `N passed, 0 failed, 0 stubs, 0 errors` line). `FINAL TEST SUMMARY` total moved from
|
||||
1003→1031, passed 965→993 (exactly +28, matching the new entry count precisely), failed and
|
||||
errors unchanged at 0. Noted in passing, not fixed (pre-existing, not introduced by this
|
||||
change): `print_module_summary()` is called with hardcoded `(name, 0, 0, 0, 0)` across
|
||||
*every* `WordTestSuite`-based module in the tree, including this new one — the per-module
|
||||
"X Summary: 0 passed, 0 failed..." line is decorative and always zero; the real counts are
|
||||
only in each word's own line and the global `FINAL TEST SUMMARY`. Confirmed pre-existing by
|
||||
checking `Arithmetic Words Summary` in the same boot log, not introduced here.
|
||||
|
||||
Two clusters left: inference-engine accessors (8 words), physics freeze/diagnostic (5 words).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-19T03:14:02Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-19T03:28:49Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,12 @@ void run_control_words_tests(VM * vm);
|
||||
void run_starforth_words_tests(VM * vm);
|
||||
void run_acl_words_tests(VM * vm);
|
||||
|
||||
/*
|
||||
* @brief Run tests for Q48.16 fixed-point words (Module 25)
|
||||
* @param vm Pointer to the VM instance
|
||||
*/
|
||||
void run_q48_words_tests(VM * vm);
|
||||
|
||||
/*
|
||||
* @brief Run tests for Mama FORTH vocabulary (capsule system M7.1)
|
||||
* @param vm Pointer to the VM instance
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* q48_words_test.c — POST tests for Q48.16 fixed-point words (Module 25)
|
||||
*
|
||||
* Proof-covered per proof/COVERAGE.md (StarForth_Q48_Words.thy, 17/23) but
|
||||
* had no interpreter-level POST coverage at all before this file — no test
|
||||
* file for q48_words.c existed. Values are built with Q.FROM-INT/Q.1/Q.0
|
||||
* and read back with Q.TO-INT so results print as plain readable integers;
|
||||
* pass/fail itself is judged only by vm->error (matching every other
|
||||
* WordTestSuite-based module), so exact printed values are for a human
|
||||
* reading the log, not machine-verified — same convention arithmetic_words_
|
||||
* test.c already uses for its own "Should print: N" fields.
|
||||
*/
|
||||
|
||||
#include "../include/test_runner.h"
|
||||
#include "../include/test_common.h"
|
||||
|
||||
/* Q48.16 Words Test Suites - Module 25 */
|
||||
static WordTestSuite q48_word_suites[] = {
|
||||
{
|
||||
"Q.+", {
|
||||
{"basic", "1 Q.FROM-INT 2 Q.FROM-INT Q.+ Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.-", {
|
||||
{"basic", "5 Q.FROM-INT 2 Q.FROM-INT Q.- Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.*", {
|
||||
{"basic", "3 Q.FROM-INT 4 Q.FROM-INT Q.* Q.TO-INT . CR", "Should print: 12", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q./", {
|
||||
{"basic", "10 Q.FROM-INT 2 Q.FROM-INT Q./ Q.TO-INT . CR", "Should print: 5", TEST_NORMAL, 0, 1, {0}},
|
||||
{"by_zero", "5 Q.FROM-INT 0 Q.FROM-INT Q./ Q.TO-INT . CR", "Should print: 0 (division by zero yields q=0)", TEST_EDGE_CASE, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
2, {0}
|
||||
},
|
||||
{
|
||||
"Q.ABS", {
|
||||
{"basic", "3 Q.FROM-INT Q.NEG Q.ABS Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.NEG", {
|
||||
{"basic", "5 Q.FROM-INT Q.NEG Q.TO-INT . CR", "Should print: -5", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.LOG", {
|
||||
{"of_one", "Q.1 Q.LOG Q.TO-INT . CR", "Should print: 0 (ln 1.0 = 0)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.EXP", {
|
||||
{"of_zero", "Q.0 Q.EXP Q.TO-INT . CR", "Should print: 1 (e^0 = 1)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.SQRT", {
|
||||
{"of_four", "4 Q.FROM-INT Q.SQRT Q.TO-INT . CR", "Should print: 2", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.SIN", {
|
||||
{"of_zero", "Q.0 Q.SIN Q.TO-INT . CR", "Should print: 0 (sin 0 = 0)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.COS", {
|
||||
{"of_zero", "Q.0 Q.COS Q.TO-INT . CR", "Should print: 1 (cos 0 = 1)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.FROM-INT", {
|
||||
{"round_trip", "7 Q.FROM-INT Q.TO-INT . CR", "Should print: 7", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.TO-INT", {
|
||||
{"of_one", "Q.1 Q.TO-INT . CR", "Should print: 1", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.1", {
|
||||
{"raw_value", "Q.1 . CR", "Should print: 65536", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.0", {
|
||||
{"raw_value", "Q.0 . CR", "Should print: 0", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.SCALE", {
|
||||
{"raw_value", "Q.SCALE . CR", "Should print: 65536", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.=", {
|
||||
{"equal", "Q.1 Q.1 Q.= . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
|
||||
{"not_equal", "Q.1 Q.0 Q.= . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
2, {0}
|
||||
},
|
||||
{
|
||||
"Q.<", {
|
||||
{"true", "Q.0 Q.1 Q.< . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
|
||||
{"false", "Q.1 Q.0 Q.< . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
2, {0}
|
||||
},
|
||||
{
|
||||
"Q.>", {
|
||||
{"true", "Q.1 Q.0 Q.> . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
|
||||
{"false", "Q.0 Q.1 Q.> . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
2, {0}
|
||||
},
|
||||
{
|
||||
"Q.0=", {
|
||||
{"true", "Q.0 Q.0= . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
|
||||
{"false", "Q.1 Q.0= . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
2, {0}
|
||||
},
|
||||
{
|
||||
"Q.MAX", {
|
||||
{"basic", "3 Q.FROM-INT 7 Q.FROM-INT Q.MAX Q.TO-INT . CR", "Should print: 7", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.MIN", {
|
||||
{"basic", "3 Q.FROM-INT 7 Q.FROM-INT Q.MIN Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{
|
||||
"Q.PRINT", {
|
||||
{"of_one", "Q.1 Q.PRINT CR", "Should print: 1.00000", TEST_NORMAL, 0, 1, {0}},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
1, {0}
|
||||
},
|
||||
{NULL, {{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}}, 0, {0}}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Executes all Q48.16 fixed-point word test suites
|
||||
* @param vm Pointer to the Forth virtual machine instance
|
||||
* @details Covers all 23 words registered by register_q48_words()
|
||||
* (q48_words.c): arithmetic (+, -, *, /), sign (ABS, NEG),
|
||||
* transcendental approximations (LOG, EXP, SQRT, SIN, COS),
|
||||
* conversions (FROM-INT, TO-INT), constants (1, 0, SCALE),
|
||||
* comparisons (=, <, >, 0=, MAX, MIN), and PRINT.
|
||||
*/
|
||||
void run_q48_words_tests(VM *vm) {
|
||||
log_message(LOG_INFO, "Running Q48.16 Words Tests (Module 25)...");
|
||||
|
||||
WordContract mod = {CONTRACT_PHYSICS_TRANSPARENT, 0};
|
||||
for (int i = 0; q48_word_suites[i].word_name != NULL; i++) {
|
||||
log_message(LOG_TEST, "▶ Testing module: %s", __FILE__);
|
||||
run_test_suite_m(vm, &q48_word_suites[i], mod);
|
||||
}
|
||||
|
||||
print_module_summary("Q48.16 Words", 0, 0, 0, 0);
|
||||
}
|
||||
@@ -130,6 +130,7 @@ static TestModule test_modules[] = {
|
||||
{"StarForth Words", NULL, 0, run_starforth_words_tests}, /* Module 22 */
|
||||
{"ACL Words", NULL, 0, run_acl_words_tests}, /* Module 23: Word-Level ACL System */
|
||||
{"Mama FORTH Words", NULL, 0, run_mama_forth_words_tests}, /* Module 24: Capsule System M7.1 */
|
||||
{"Q48.16 Words", NULL, 0, run_q48_words_tests}, /* Module 25: Q48.16 Fixed-Point Arithmetic */
|
||||
{NULL, NULL, 0, NULL} /* End marker */
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user