diff --git a/test-basic-module-cpp/src/test_basic_module_cpp_impl.cpp b/test-basic-module-cpp/src/test_basic_module_cpp_impl.cpp index abf8fee..585a898 100644 --- a/test-basic-module-cpp/src/test_basic_module_cpp_impl.cpp +++ b/test-basic-module-cpp/src/test_basic_module_cpp_impl.cpp @@ -21,12 +21,35 @@ bool TestBasicModuleCppImpl::returnTrue() { return true; } bool TestBasicModuleCppImpl::returnFalse() { return false; } bool TestBasicModuleCppImpl::isPositive(int64_t value) { return value > 0; } +namespace { + +// A character is a Unicode CODE POINT, so count the bytes that START one: in +// UTF-8 every continuation byte matches 10xxxxxx. Identical to the helper in +// test_basic_module — these two modules are deliberate mirrors and must agree +// on non-ASCII, which they did not: this one answered in BYTES while its twin +// answered in characters. +inline bool isContinuationByte(char c) +{ + return (static_cast(c) & 0xC0) == 0x80; +} + +int64_t characterCount(const std::string& s) +{ + int64_t n = 0; + for (char c : s) { + if (!isContinuationByte(c)) ++n; + } + return n; +} + +} // namespace + // ── int64_t ────────────────────────────────────────────────────────────── int64_t TestBasicModuleCppImpl::returnInt() { return 42; } int64_t TestBasicModuleCppImpl::addInts(int64_t a, int64_t b) { return a + b; } int64_t TestBasicModuleCppImpl::stringLength(const std::string& s) { - return static_cast(s.size()); + return characterCount(s); } // ── uint64_t ───────────────────────────────────────────────────────────── @@ -81,7 +104,7 @@ StdLogosResult TestBasicModuleCppImpl::validateInput(const std::string& input) { } nlohmann::json data; data["input"] = input; - data["length"] = static_cast(input.size()); + data["length"] = characterCount(input); return {true, data, ""}; } diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 2423f5e..e1eeb53 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -508,6 +508,14 @@ test_basic_cpp "addInts(3, 4)" "Result: 7" "test_basic_module_cpp.addInts test_basic_cpp "addInts(0, 0)" "Result: 0" "test_basic_module_cpp.addInts(0, 0)" test_basic_cpp "addInts(-5, 10)" "Result: 5" "test_basic_module_cpp.addInts(-5, 10)" test_basic_cpp "stringLength(hello)" "Result: 5" "test_basic_module_cpp.stringLength(hello)" +# The two modules are deliberate MIRRORS, so they must agree on non-ASCII. They +# did not: this one answered in BYTES (6) while test_basic_module answered in +# CHARACTERS (5). The blind spot was that this group only ever asserted ASCII, +# where bytes and characters coincide — so the disagreement was invisible. +test_basic_cpp "stringLength(héllo) [characters, mirrors test_basic_module]" "Result: 5" \ + "test_basic_module_cpp.stringLength(héllo)" +test_basic_cpp "stringLength(😀) [non-BMP character counts 1]" "Result: 1" \ + "test_basic_module_cpp.stringLength(😀)" skip_test "stringLength()" "logoscore cannot call 1-arg method with 0 args" # ── Return type: uint64_t (unique to the C++ surface) ───────────────────────