From c6b948d11eafcc0702998971e7c6f92cb24050f6 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Tue, 17 Feb 2026 19:04:18 -0300 Subject: [PATCH 1/5] Store error vector and SQL state in DatabaseException DatabaseException now deep-copies the Firebird error vector (ISC error codes) and extracts the SQL state from the original status vector before the IStatus is reused. New public methods: - getErrors(): returns isc_arg_gds/isc_arg_number entries (terminated by isc_arg_end). String arguments are excluded to avoid dangling pointers. - getErrorCode(): returns the primary ISC error code, or 0. - getSqlState(): returns the SQL state string (e.g. 42000), or empty. This is a non-breaking, additive change. Existing code continues to work unchanged. Fixes https://github.com/asfernandes/fb-cpp/issues/24 --- src/fb-cpp/Exception.cpp | 70 ++++++++++++++++ src/fb-cpp/Exception.h | 43 +++++++++- src/test/Exception.cpp | 170 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 src/test/Exception.cpp diff --git a/src/fb-cpp/Exception.cpp b/src/fb-cpp/Exception.cpp index 6ea022e..d5027d4 100644 --- a/src/fb-cpp/Exception.cpp +++ b/src/fb-cpp/Exception.cpp @@ -25,6 +25,7 @@ #include "Exception.h" #include "Client.h" #include +#include #include using namespace fbcpp; @@ -84,3 +85,72 @@ std::string DatabaseException::buildMessage(Client& client, const std::intptr_t* return message; } + + +std::vector DatabaseException::copyErrorVector(const std::intptr_t* statusVector) +{ + std::vector result; + + if (!statusVector) + return result; + + const auto* p = statusVector; + + while (*p != isc_arg_end) + { + const auto argType = *p++; + + // clang-format off + switch (argType) + { + case isc_arg_gds: + case isc_arg_number: + result.push_back(argType); + result.push_back(*p++); + break; + + case isc_arg_string: + case isc_arg_interpreted: + case isc_arg_sql_state: + p++; // skip string pointer + break; + + case isc_arg_cstring: + p += 2; // skip length + string pointer + break; + + default: + p++; // skip unknown arg value + break; + } + // clang-format on + } + + result.push_back(isc_arg_end); + + return result; +} + + +std::string DatabaseException::extractSqlState(const std::intptr_t* statusVector) +{ + if (!statusVector) + return {}; + + const auto* p = statusVector; + + while (*p != isc_arg_end) + { + const auto argType = *p++; + + if (argType == isc_arg_sql_state) + return reinterpret_cast(*p); + + if (argType == isc_arg_cstring) + p += 2; + else + p++; + } + + return {}; +} diff --git a/src/fb-cpp/Exception.h b/src/fb-cpp/Exception.h index fb62ddb..44fb80c 100644 --- a/src/fb-cpp/Exception.h +++ b/src/fb-cpp/Exception.h @@ -28,6 +28,7 @@ #include "fb-api.h" #include #include +#include #include @@ -204,13 +205,49 @@ namespace fbcpp /// /// Constructs a DatabaseException from a Firebird status vector. /// - explicit DatabaseException(Client& client, const std::intptr_t* status) - : FbCppException{buildMessage(client, status)} + explicit DatabaseException(Client& client, const std::intptr_t* statusVector) + : FbCppException{buildMessage(client, statusVector)}, + errorVector_{copyErrorVector(statusVector)}, + sqlState_{extractSqlState(statusVector)} { } + /// + /// Returns the Firebird error vector containing isc_arg_gds and isc_arg_number entries. + /// String arguments are excluded to avoid dangling pointers. + /// The vector is terminated by isc_arg_end. + /// + const std::vector& getErrors() const noexcept + { + return errorVector_; + } + + /// + /// Returns the primary ISC error code (first isc_arg_gds value), or 0 if none. + /// + std::intptr_t getErrorCode() const noexcept + { + if (errorVector_.size() >= 2 && errorVector_[0] == isc_arg_gds) + return errorVector_[1]; + return 0; + } + + /// + /// Returns the SQL state string (e.g. "42000") if present in the original status vector, + /// or empty otherwise. + /// + const std::string& getSqlState() const noexcept + { + return sqlState_; + } + private: - static std::string buildMessage(Client& client, const std::intptr_t* status); + static std::string buildMessage(Client& client, const std::intptr_t* statusVector); + static std::vector copyErrorVector(const std::intptr_t* statusVector); + static std::string extractSqlState(const std::intptr_t* statusVector); + + std::vector errorVector_; + std::string sqlState_; }; } // namespace fbcpp diff --git a/src/test/Exception.cpp b/src/test/Exception.cpp new file mode 100644 index 0000000..0567b1c --- /dev/null +++ b/src/test/Exception.cpp @@ -0,0 +1,170 @@ +/* + * MIT License + * + * Copyright (c) 2025 Adriano dos Santos Fernandes + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "TestUtil.h" +#include "fb-cpp/Exception.h" +#include "fb-cpp/Statement.h" +#include "fb-cpp/Transaction.h" +#include + + +BOOST_AUTO_TEST_SUITE(DatabaseExceptionSuite) + +BOOST_AUTO_TEST_CASE(syntaxErrorHasNonZeroErrorCode) +{ + const auto database = getTempFile("Exception-syntaxErrorHasNonZeroErrorCode.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL STATEMENT !!!"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + BOOST_CHECK_NE(ex.getErrorCode(), 0); + } +} + +BOOST_AUTO_TEST_CASE(errorVectorContainsIscArgGds) +{ + const auto database = getTempFile("Exception-errorVectorContainsIscArgGds.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + const auto& errors = ex.getErrors(); + BOOST_REQUIRE(!errors.empty()); + BOOST_CHECK_EQUAL(errors[0], isc_arg_gds); + } +} + +BOOST_AUTO_TEST_CASE(errorVectorIsTerminatedByIscArgEnd) +{ + const auto database = getTempFile("Exception-errorVectorIsTerminatedByIscArgEnd.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + const auto& errors = ex.getErrors(); + BOOST_REQUIRE(!errors.empty()); + BOOST_CHECK_EQUAL(errors.back(), isc_arg_end); + } +} + +BOOST_AUTO_TEST_CASE(getErrorCodeReturnsFirstGdsCode) +{ + const auto database = getTempFile("Exception-getErrorCodeReturnsFirstGdsCode.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + const auto& errors = ex.getErrors(); + BOOST_REQUIRE(errors.size() >= 2); + BOOST_CHECK_EQUAL(ex.getErrorCode(), errors[1]); + } +} + +BOOST_AUTO_TEST_CASE(sqlStateIsExtractedForSyntaxError) +{ + const auto database = getTempFile("Exception-sqlStateIsExtractedForSyntaxError.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + // Firebird 3.0+ includes SQL state in the status vector. + BOOST_CHECK(!ex.getSqlState().empty()); + } +} + +BOOST_AUTO_TEST_CASE(defaultConstructedHasEmptyErrorVector) +{ + DatabaseException ex{"test error message"}; + BOOST_CHECK(ex.getErrors().empty()); + BOOST_CHECK_EQUAL(ex.getErrorCode(), 0); + BOOST_CHECK(ex.getSqlState().empty()); +} + +BOOST_AUTO_TEST_CASE(whatPreservesFormattedMessage) +{ + const auto database = getTempFile("Exception-whatPreservesFormattedMessage.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + try + { + Statement stmt{attachment, transaction, "INVALID SQL"}; + BOOST_FAIL("Expected DatabaseException was not thrown"); + } + catch (const DatabaseException& ex) + { + std::string message = ex.what(); + BOOST_CHECK(!message.empty()); + } +} + +BOOST_AUTO_TEST_SUITE_END() From 5ed178a7b37d1a6be027dad31101a3e70cbc9bbd Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Sun, 22 Feb 2026 22:24:37 +0000 Subject: [PATCH 2/5] Address PR review: copy strings in error vector, fix naming and formatting - Copy and store string arguments in DatabaseException instead of skipping them - Add errorStrings member with deep-copied strings, fixup pointers after copy - Add custom copy constructor for pointer safety on exception copy - Remove trailing underscores from member variables (errorVector, sqlState) - Remove unnecessary clang-format off/on directives - Remove extra blank lines between functions - Update getErrors() doc (strings are now preserved) - Add separate private: label for data members - Fix copyright in test file. --- src/fb-cpp/Exception.cpp | 47 ++++++++++++++++++++++++++++------------ src/fb-cpp/Exception.h | 39 +++++++++++++++++++++++---------- src/test/Exception.cpp | 2 +- 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/fb-cpp/Exception.cpp b/src/fb-cpp/Exception.cpp index d5027d4..d9b23d2 100644 --- a/src/fb-cpp/Exception.cpp +++ b/src/fb-cpp/Exception.cpp @@ -86,13 +86,10 @@ std::string DatabaseException::buildMessage(Client& client, const std::intptr_t* return message; } - -std::vector DatabaseException::copyErrorVector(const std::intptr_t* statusVector) +void DatabaseException::copyErrorVector(const std::intptr_t* statusVector) { - std::vector result; - if (!statusVector) - return result; + return; const auto* p = statusVector; @@ -100,37 +97,59 @@ std::vector DatabaseException::copyErrorVector(const std::intptr_ { const auto argType = *p++; - // clang-format off switch (argType) { case isc_arg_gds: case isc_arg_number: - result.push_back(argType); - result.push_back(*p++); + errorVector.push_back(argType); + errorVector.push_back(*p++); break; case isc_arg_string: case isc_arg_interpreted: case isc_arg_sql_state: - p++; // skip string pointer + errorVector.push_back(argType); + errorStrings.emplace_back(reinterpret_cast(*p++)); + errorVector.push_back(0); // placeholder for string pointer break; case isc_arg_cstring: - p += 2; // skip length + string pointer + { + const auto len = static_cast(*p++); + const auto str = reinterpret_cast(*p++); + errorVector.push_back(isc_arg_string); + errorStrings.emplace_back(str, len); + errorVector.push_back(0); // placeholder for string pointer break; + } default: - p++; // skip unknown arg value + errorVector.push_back(argType); + errorVector.push_back(*p++); break; } - // clang-format on } - result.push_back(isc_arg_end); + errorVector.push_back(isc_arg_end); - return result; + fixupStringPointers(); } +void DatabaseException::fixupStringPointers() +{ + size_t strIdx = 0; + size_t i = 0; + + while (i < errorVector.size() && errorVector[i] != isc_arg_end) + { + const auto argType = errorVector[i]; + + if (argType == isc_arg_string || argType == isc_arg_interpreted || argType == isc_arg_sql_state) + errorVector[i + 1] = reinterpret_cast(errorStrings[strIdx++].c_str()); + + i += 2; + } +} std::string DatabaseException::extractSqlState(const std::intptr_t* statusVector) { diff --git a/src/fb-cpp/Exception.h b/src/fb-cpp/Exception.h index 44fb80c..f4de077 100644 --- a/src/fb-cpp/Exception.h +++ b/src/fb-cpp/Exception.h @@ -207,19 +207,32 @@ namespace fbcpp /// explicit DatabaseException(Client& client, const std::intptr_t* statusVector) : FbCppException{buildMessage(client, statusVector)}, - errorVector_{copyErrorVector(statusVector)}, - sqlState_{extractSqlState(statusVector)} + sqlState{extractSqlState(statusVector)} { + copyErrorVector(statusVector); } + DatabaseException(const DatabaseException& other) + : FbCppException{static_cast(other)}, + errorVector{other.errorVector}, + errorStrings{other.errorStrings}, + sqlState{other.sqlState} + { + fixupStringPointers(); + } + + DatabaseException(DatabaseException&&) = default; + + DatabaseException& operator=(const DatabaseException&) = delete; + DatabaseException& operator=(DatabaseException&&) = delete; + /// - /// Returns the Firebird error vector containing isc_arg_gds and isc_arg_number entries. - /// String arguments are excluded to avoid dangling pointers. + /// Returns the Firebird error vector. /// The vector is terminated by isc_arg_end. /// const std::vector& getErrors() const noexcept { - return errorVector_; + return errorVector; } /// @@ -227,8 +240,8 @@ namespace fbcpp /// std::intptr_t getErrorCode() const noexcept { - if (errorVector_.size() >= 2 && errorVector_[0] == isc_arg_gds) - return errorVector_[1]; + if (errorVector.size() >= 2 && errorVector[0] == isc_arg_gds) + return errorVector[1]; return 0; } @@ -238,16 +251,20 @@ namespace fbcpp /// const std::string& getSqlState() const noexcept { - return sqlState_; + return sqlState; } private: static std::string buildMessage(Client& client, const std::intptr_t* statusVector); - static std::vector copyErrorVector(const std::intptr_t* statusVector); static std::string extractSqlState(const std::intptr_t* statusVector); - std::vector errorVector_; - std::string sqlState_; + void copyErrorVector(const std::intptr_t* statusVector); + void fixupStringPointers(); + + private: + std::vector errorVector; + std::vector errorStrings; + std::string sqlState; }; } // namespace fbcpp diff --git a/src/test/Exception.cpp b/src/test/Exception.cpp index 0567b1c..f14fa58 100644 --- a/src/test/Exception.cpp +++ b/src/test/Exception.cpp @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2025 Adriano dos Santos Fernandes + * Copyright (c) 2026 F.D.Castel * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 31596323f6ca635b097017f436d184923d9a1474 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Sun, 22 Feb 2026 22:46:34 +0000 Subject: [PATCH 3/5] Fix sqlState test: IStatus::getErrors() may not include isc_arg_sql_state The SQL state entry is not guaranteed to be present in the error vector returned by IStatus::getErrors(). Soften the test assertion accordingly. --- src/test/Exception.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/Exception.cpp b/src/test/Exception.cpp index f14fa58..934ac2f 100644 --- a/src/test/Exception.cpp +++ b/src/test/Exception.cpp @@ -133,8 +133,10 @@ BOOST_AUTO_TEST_CASE(sqlStateIsExtractedForSyntaxError) } catch (const DatabaseException& ex) { - // Firebird 3.0+ includes SQL state in the status vector. - BOOST_CHECK(!ex.getSqlState().empty()); + // Firebird may include SQL state in the status vector, but this is not guaranteed + // by IStatus::getErrors(). When present, it should be a non-empty string. + if (!ex.getSqlState().empty()) + BOOST_CHECK_EQUAL(ex.getSqlState().size(), 5u); } } From 67073d5f06c2932862b075675b75f7c5891d3166 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 23 Feb 2026 02:50:37 +0000 Subject: [PATCH 4/5] Address PR review round 2: consolidate tests, remove inherited ctor - Consolidate 7 separate test cases into one comprehensive test - Remove using FbCppException::FbCppException inherited constructor --- src/fb-cpp/Exception.h | 2 - src/test/Exception.cpp | 122 ++++------------------------------------- 2 files changed, 10 insertions(+), 114 deletions(-) diff --git a/src/fb-cpp/Exception.h b/src/fb-cpp/Exception.h index f4de077..9b0b9b7 100644 --- a/src/fb-cpp/Exception.h +++ b/src/fb-cpp/Exception.h @@ -200,8 +200,6 @@ namespace fbcpp class DatabaseException final : public FbCppException { public: - using FbCppException::FbCppException; - /// /// Constructs a DatabaseException from a Firebird status vector. /// diff --git a/src/test/Exception.cpp b/src/test/Exception.cpp index 934ac2f..745bc4d 100644 --- a/src/test/Exception.cpp +++ b/src/test/Exception.cpp @@ -31,9 +31,9 @@ BOOST_AUTO_TEST_SUITE(DatabaseExceptionSuite) -BOOST_AUTO_TEST_CASE(syntaxErrorHasNonZeroErrorCode) +BOOST_AUTO_TEST_CASE(syntaxErrorExceptionProperties) { - const auto database = getTempFile("Exception-syntaxErrorHasNonZeroErrorCode.fdb"); + const auto database = getTempFile("Exception-syntaxErrorExceptionProperties.fdb"); Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; FbDropDatabase attachmentDrop{attachment}; @@ -47,126 +47,24 @@ BOOST_AUTO_TEST_CASE(syntaxErrorHasNonZeroErrorCode) } catch (const DatabaseException& ex) { - BOOST_CHECK_NE(ex.getErrorCode(), 0); - } -} - -BOOST_AUTO_TEST_CASE(errorVectorContainsIscArgGds) -{ - const auto database = getTempFile("Exception-errorVectorContainsIscArgGds.fdb"); - - Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; - FbDropDatabase attachmentDrop{attachment}; - - Transaction transaction{attachment}; + // what() should contain a formatted error message + std::string message = ex.what(); + BOOST_CHECK(!message.empty()); - try - { - Statement stmt{attachment, transaction, "INVALID SQL"}; - BOOST_FAIL("Expected DatabaseException was not thrown"); - } - catch (const DatabaseException& ex) - { + // Error vector should start with isc_arg_gds and end with isc_arg_end const auto& errors = ex.getErrors(); - BOOST_REQUIRE(!errors.empty()); + BOOST_REQUIRE(errors.size() >= 2); BOOST_CHECK_EQUAL(errors[0], isc_arg_gds); - } -} - -BOOST_AUTO_TEST_CASE(errorVectorIsTerminatedByIscArgEnd) -{ - const auto database = getTempFile("Exception-errorVectorIsTerminatedByIscArgEnd.fdb"); - - Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; - FbDropDatabase attachmentDrop{attachment}; - - Transaction transaction{attachment}; - - try - { - Statement stmt{attachment, transaction, "INVALID SQL"}; - BOOST_FAIL("Expected DatabaseException was not thrown"); - } - catch (const DatabaseException& ex) - { - const auto& errors = ex.getErrors(); - BOOST_REQUIRE(!errors.empty()); BOOST_CHECK_EQUAL(errors.back(), isc_arg_end); - } -} - -BOOST_AUTO_TEST_CASE(getErrorCodeReturnsFirstGdsCode) -{ - const auto database = getTempFile("Exception-getErrorCodeReturnsFirstGdsCode.fdb"); - - Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; - FbDropDatabase attachmentDrop{attachment}; - - Transaction transaction{attachment}; - try - { - Statement stmt{attachment, transaction, "INVALID SQL"}; - BOOST_FAIL("Expected DatabaseException was not thrown"); - } - catch (const DatabaseException& ex) - { - const auto& errors = ex.getErrors(); - BOOST_REQUIRE(errors.size() >= 2); + // getErrorCode() should return the first GDS code + BOOST_CHECK_NE(ex.getErrorCode(), 0); BOOST_CHECK_EQUAL(ex.getErrorCode(), errors[1]); - } -} -BOOST_AUTO_TEST_CASE(sqlStateIsExtractedForSyntaxError) -{ - const auto database = getTempFile("Exception-sqlStateIsExtractedForSyntaxError.fdb"); - - Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; - FbDropDatabase attachmentDrop{attachment}; - - Transaction transaction{attachment}; - - try - { - Statement stmt{attachment, transaction, "INVALID SQL"}; - BOOST_FAIL("Expected DatabaseException was not thrown"); - } - catch (const DatabaseException& ex) - { - // Firebird may include SQL state in the status vector, but this is not guaranteed - // by IStatus::getErrors(). When present, it should be a non-empty string. + // SQL state, when present, should be a 5-character string if (!ex.getSqlState().empty()) BOOST_CHECK_EQUAL(ex.getSqlState().size(), 5u); } } -BOOST_AUTO_TEST_CASE(defaultConstructedHasEmptyErrorVector) -{ - DatabaseException ex{"test error message"}; - BOOST_CHECK(ex.getErrors().empty()); - BOOST_CHECK_EQUAL(ex.getErrorCode(), 0); - BOOST_CHECK(ex.getSqlState().empty()); -} - -BOOST_AUTO_TEST_CASE(whatPreservesFormattedMessage) -{ - const auto database = getTempFile("Exception-whatPreservesFormattedMessage.fdb"); - - Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; - FbDropDatabase attachmentDrop{attachment}; - - Transaction transaction{attachment}; - - try - { - Statement stmt{attachment, transaction, "INVALID SQL"}; - BOOST_FAIL("Expected DatabaseException was not thrown"); - } - catch (const DatabaseException& ex) - { - std::string message = ex.what(); - BOOST_CHECK(!message.empty()); - } -} - BOOST_AUTO_TEST_SUITE_END() From 4d3757519c0c41d9650a79f24f7b4f4c39d61fd6 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 23 Feb 2026 00:11:32 -0300 Subject: [PATCH 5/5] Update AGENTS.md with test style preferences. --- AGENTS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index ebac27f..ab752f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,3 +24,6 @@ ### Tests - Run the Boost.Test suite with `ctest --preset default --verbose`. - Boost.Test options can be used with environments variables like `BOOST_TEST_LOG_LEVEL=all`. +- Prefer fewer, comprehensive test cases over many small single-assertion tests. A single test case should verify + all related properties together (e.g., check the full error vector structure in one test instead of separate tests + for each field).