From a5f4ea2416d94616978545d9e97f8df7150110fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:04:27 +0000 Subject: [PATCH 1/3] Initial plan From ce6cbfa2a0d0a6304d6105fb8c4072b5fa4c93b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:22:31 +0000 Subject: [PATCH 2/3] Add ApiSignature unit tests and fix NULL dereference crashes Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- src/common/dmod_common.c | 27 ++ tests/system/public/CMakeLists.txt | 1 + .../public/tests_dmod_api_signature.cpp | 436 ++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 tests/system/public/tests_dmod_api_signature.cpp diff --git a/src/common/dmod_common.c b/src/common/dmod_common.c index eba8b79..97c4a40 100644 --- a/src/common/dmod_common.c +++ b/src/common/dmod_common.c @@ -230,6 +230,10 @@ bool Dmod_ApiSignature_ReadModuleName( const char* Signature, char* ModuleName, { return false; } + if( ModuleName == NULL ) + { + return false; + } if( !Dmod_ApiSignature_IsValid( Signature ) ) { return false; @@ -275,6 +279,10 @@ bool Dmod_ApiSignature_ReadVersion( const char* Signature, char* Version, size_t { return false; } + if( Version == NULL ) + { + return false; + } if( !Dmod_ApiSignature_IsValid( Signature ) ) { return false; @@ -315,6 +323,10 @@ bool Dmod_ApiSignature_ReadModuleVersion( const char* Signature, char* ModuleVer { return false; } + if( ModuleVersion == NULL ) + { + return false; + } if( !Dmod_ApiSignature_IsValid( Signature ) ) { return false; @@ -500,6 +512,11 @@ static bool ApiSignature_AreModulesEqual( const char* Signature1, const char* Si const char* module1 = ApiSignature_GetModule( Signature1 ); const char* module2 = ApiSignature_GetModule( Signature2 ); + if( module1 == NULL || module2 == NULL ) + { + return module1 == module2; + } + while( *module1 != '\0' && *module2 != '\0' && *module1 != ':' && *module2 != ':' ) { if( *module1 != *module2 ) @@ -526,6 +543,11 @@ static bool ApiSignature_AreApiVersionsCompatible( const char* Signature1, const const char* version1 = ApiSignature_GetVersion( Signature1 ); const char* version2 = ApiSignature_GetVersion( Signature2 ); + if( version1 == NULL || version2 == NULL ) + { + return version1 == version2; + } + while( *version1 != '\0' && *version2 != '\0' && *version1 != '/' && *version2 != '/' && *version1 != '.' && *version2 != '.' ) { if( *version1 != *version2 ) @@ -553,6 +575,11 @@ static bool ApiSignature_AreModuleVersionsCompatible( const char* Signature1, co const char* moduleVersion1 = ApiSignature_GetModuleVersion( Signature1 ); const char* moduleVersion2 = ApiSignature_GetModuleVersion( Signature2 ); + if( moduleVersion1 == NULL || moduleVersion2 == NULL ) + { + return moduleVersion1 == moduleVersion2; + } + while( *moduleVersion1 != '\0' && *moduleVersion2 != '\0' && *moduleVersion1 != '.' && *moduleVersion2 != '.' ) { if( *moduleVersion1 != *moduleVersion2 ) diff --git a/tests/system/public/CMakeLists.txt b/tests/system/public/CMakeLists.txt index 1c1efcc..a9eb033 100644 --- a/tests/system/public/CMakeLists.txt +++ b/tests/system/public/CMakeLists.txt @@ -8,5 +8,6 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_loadfile_package_path.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_irq.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_verify_apis.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_api_signature.cpp PARENT_SCOPE ) diff --git a/tests/system/public/tests_dmod_api_signature.cpp b/tests/system/public/tests_dmod_api_signature.cpp new file mode 100644 index 0000000..8958c3e --- /dev/null +++ b/tests/system/public/tests_dmod_api_signature.cpp @@ -0,0 +1,436 @@ +#define DMOD_PRIVATE +#include +#include +#include +#include "dmod.h" + +// =============================================================== +// Test fixture +// =============================================================== + +// Note on version format in DMOD_MAKE_SIGNATURE: +// DMOD_MAKE_SIGNATURE(MODULE, VERSION, FUNC) stringifies VERSION as a literal +// token sequence. Use `API/MODULE` (e.g. 1/1) to produce a version string with +// both an API version and a module version separated by '/'. Use a dotted value +// (e.g. 1.0) to produce a simple version string with no module version part. + +class DmodApiSignatureTest : public ::testing::Test +{ +protected: + void SetUp() override {} + void TearDown() override {} +}; + +// =============================================================== +// Tests for Dmod_ApiSignature_IsValid +// =============================================================== + +/** + * @brief A standard DMOD signature must be valid. + */ +TEST_F(DmodApiSignatureTest, IsValidDmodSignature) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsValid(sig)); +} + +/** + * @brief A builtin (DMBI) signature must be valid. + */ +TEST_F(DmodApiSignatureTest, IsValidBuiltinSignature) +{ + static const char* sig = DMOD_MAKE_BUILTIN_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsValid(sig)); +} + +/** + * @brief A MAL signature must be valid. + */ +TEST_F(DmodApiSignatureTest, IsValidMalSignature) +{ + static const char* sig = DMOD_MAKE_MAL_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsValid(sig)); +} + +/** + * @brief NULL must not be valid. + */ +TEST_F(DmodApiSignatureTest, IsValidNullReturnsFalse) +{ + EXPECT_FALSE(Dmod_ApiSignature_IsValid(nullptr)); +} + +/** + * @brief A plain string without a proper prefix must not be valid. + */ +TEST_F(DmodApiSignatureTest, IsValidPlainStringReturnsFalse) +{ + EXPECT_FALSE(Dmod_ApiSignature_IsValid("TestFunc@TestModule:1/1")); +} + +// =============================================================== +// Tests for Dmod_ApiSignature_AreCompatible +// =============================================================== + +/** + * @brief Two identical signatures must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleIdenticalSignatures) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig, sig)); +} + +/** + * @brief Two independently-created signatures with the same name, module, and + * version must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleSameNameModuleVersion) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures with the same major API version and the same major module + * version but different minor module versions must be compatible. + * e.g. "1/1.0" and "1/1.3" — minor module version difference is allowed. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDifferentMinorModuleVersions) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1.0, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1/1.3, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures with different function names must not be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDifferentNameReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, FuncA); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, FuncB); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures with different module names must not be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDifferentModuleReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(ModuleA, 1/1, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(ModuleB, 1/1, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures with different major API versions must not be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDifferentMajorApiVersionReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 2/1, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures with different major module versions must not be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDifferentMajorModuleVersionReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1/2, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Signatures using a simple dotted version (no '/' separator for module + * version) must not crash and must be compatible when all other fields match. + * + * This is the regression test for the NULL-dereference crash that occurred when + * the version string did not contain a '/' character. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleSimpleDotVersionNoCrash) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1.0, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1.0, TestFunc); + // Both lack a module version (no '/') -> compatible + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Two signatures with a simple version but different names must not crash + * and must return false. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleSimpleDotVersionDifferentNameReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1.0, FuncA); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1.0, FuncB); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief One signature has a module version ('/') and the other does not: + * they must not be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMixedVersionFormatReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* sig2 = DMOD_MAKE_SIGNATURE(TestModule, 1.0, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Passing NULL as the first signature must return false without crashing. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleNullFirstReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(nullptr, sig)); +} + +/** + * @brief Passing NULL as the second signature must return false without crashing. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleNullSecondReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig, nullptr)); +} + +/** + * @brief Passing two NULLs must return false without crashing. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleBothNullReturnsFalse) +{ + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(nullptr, nullptr)); +} + +/** + * @brief An invalid (unprefixed) signature must return false. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleInvalidSignatureReturnsFalse) +{ + static const char* invalid = "TestFunc@TestModule:1/1"; + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(invalid, sig)); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig, invalid)); +} + +/** + * @brief A DMOD and a BUILTIN signature for the same API must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDmodAndBuiltinSameApi) +{ + static const char* dmodSig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* builtinSig = DMOD_MAKE_BUILTIN_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(dmodSig, builtinSig)); +} + +// =============================================================== +// Tests for Dmod_ApiSignature_GetName / GetVersion / +// GetModule / GetModuleVersion +// =============================================================== + +/** + * @brief GetName must return a pointer that starts with the function name. + */ +TEST_F(DmodApiSignatureTest, GetNameReturnsNamePart) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, MyFunc); + const char* name = Dmod_ApiSignature_GetName(sig); + ASSERT_NE(name, nullptr); + EXPECT_EQ(name[0], 'M'); + EXPECT_EQ(name[1], 'y'); +} + +/** + * @brief GetName with NULL must return NULL. + */ +TEST_F(DmodApiSignatureTest, GetNameNullReturnsNull) +{ + EXPECT_EQ(Dmod_ApiSignature_GetName(nullptr), nullptr); +} + +/** + * @brief GetVersion must return a pointer that starts with the API version digit. + */ +TEST_F(DmodApiSignatureTest, GetVersionReturnsVersionPart) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 2/5, MyFunc); + const char* version = Dmod_ApiSignature_GetVersion(sig); + ASSERT_NE(version, nullptr); + EXPECT_EQ(version[0], '2'); +} + +/** + * @brief GetModuleVersion must return a pointer that starts with the module + * version digit (the part after '/'). + */ +TEST_F(DmodApiSignatureTest, GetModuleVersionReturnsModuleVersionPart) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/3, MyFunc); + const char* moduleVersion = Dmod_ApiSignature_GetModuleVersion(sig); + ASSERT_NE(moduleVersion, nullptr); + EXPECT_EQ(moduleVersion[0], '3'); +} + +/** + * @brief GetModuleVersion must return NULL when the version has no '/' separator. + */ +TEST_F(DmodApiSignatureTest, GetModuleVersionReturnsNullForSimpleVersion) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1.0, MyFunc); + EXPECT_EQ(Dmod_ApiSignature_GetModuleVersion(sig), nullptr); +} + +/** + * @brief GetModule must return a pointer that starts with the module name. + */ +TEST_F(DmodApiSignatureTest, GetModuleReturnsModulePart) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(SampleModule, 1/1, MyFunc); + const char* module = Dmod_ApiSignature_GetModule(sig); + ASSERT_NE(module, nullptr); + EXPECT_EQ(module[0], 'S'); +} + +// =============================================================== +// Tests for Dmod_ApiSignature_ReadModuleName / +// ReadVersion / ReadModuleVersion +// =============================================================== + +/** + * @brief ReadModuleName must correctly copy the module name into the buffer. + */ +TEST_F(DmodApiSignatureTest, ReadModuleNameCopiesCorrectly) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(SampleModule, 1/1, MyFunc); + char buf[64] = {}; + ASSERT_TRUE(Dmod_ApiSignature_ReadModuleName(sig, buf, sizeof(buf))); + EXPECT_STREQ(buf, "SampleModule"); +} + +/** + * @brief ReadModuleName must return false when the output buffer is NULL. + */ +TEST_F(DmodApiSignatureTest, ReadModuleNameNullBufferReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, MyFunc); + EXPECT_FALSE(Dmod_ApiSignature_ReadModuleName(sig, nullptr, 64)); +} + +/** + * @brief ReadModuleName must return false when MaxLength is zero. + */ +TEST_F(DmodApiSignatureTest, ReadModuleNameZeroLengthReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, MyFunc); + char buf[64] = {}; + EXPECT_FALSE(Dmod_ApiSignature_ReadModuleName(sig, buf, 0)); +} + +/** + * @brief ReadVersion must correctly copy the API version string into the buffer. + */ +TEST_F(DmodApiSignatureTest, ReadVersionCopiesCorrectly) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 2/5, MyFunc); + char buf[32] = {}; + ASSERT_TRUE(Dmod_ApiSignature_ReadVersion(sig, buf, sizeof(buf))); + EXPECT_EQ(buf[0], '2'); +} + +/** + * @brief ReadVersion must return false when the output buffer is NULL. + */ +TEST_F(DmodApiSignatureTest, ReadVersionNullBufferReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, MyFunc); + EXPECT_FALSE(Dmod_ApiSignature_ReadVersion(sig, nullptr, 32)); +} + +/** + * @brief ReadModuleVersion must correctly copy the module version into the buffer. + */ +TEST_F(DmodApiSignatureTest, ReadModuleVersionCopiesCorrectly) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/7, MyFunc); + char buf[32] = {}; + ASSERT_TRUE(Dmod_ApiSignature_ReadModuleVersion(sig, buf, sizeof(buf))); + EXPECT_EQ(buf[0], '7'); +} + +/** + * @brief ReadModuleVersion must return false when the version has no '/' separator. + */ +TEST_F(DmodApiSignatureTest, ReadModuleVersionNoSeparatorReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1.0, MyFunc); + char buf[32] = {}; + EXPECT_FALSE(Dmod_ApiSignature_ReadModuleVersion(sig, buf, sizeof(buf))); +} + +/** + * @brief ReadModuleVersion must return false when the output buffer is NULL. + */ +TEST_F(DmodApiSignatureTest, ReadModuleVersionNullBufferReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, MyFunc); + EXPECT_FALSE(Dmod_ApiSignature_ReadModuleVersion(sig, nullptr, 32)); +} + +// =============================================================== +// Tests for Dmod_ApiSignature_IsModuleNameGiven / +// IsModule / IsMal / IsBuiltin +// =============================================================== + +/** + * @brief A signature with a module name must report that the module name is given. + */ +TEST_F(DmodApiSignatureTest, IsModuleNameGivenReturnsTrueForModuleSignature) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(MyModule, 1/1, MyFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsModuleNameGiven(sig)); +} + +/** + * @brief IsModule must return true when the module name matches. + */ +TEST_F(DmodApiSignatureTest, IsModuleMatchingNameReturnsTrue) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(MyModule, 1/1, MyFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsModule(sig, "MyModule")); +} + +/** + * @brief IsModule must return false when the module name does not match. + */ +TEST_F(DmodApiSignatureTest, IsModuleNonMatchingNameReturnsFalse) +{ + static const char* sig = DMOD_MAKE_SIGNATURE(MyModule, 1/1, MyFunc); + EXPECT_FALSE(Dmod_ApiSignature_IsModule(sig, "OtherModule")); +} + +/** + * @brief IsMal must return true only for MAL signatures. + */ +TEST_F(DmodApiSignatureTest, IsMalReturnsTrueForMalSignature) +{ + static const char* malSig = DMOD_MAKE_MAL_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* dmodSig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsMal(malSig)); + EXPECT_FALSE(Dmod_ApiSignature_IsMal(dmodSig)); +} + +/** + * @brief IsBuiltin must return true only for builtin (DMBI) signatures. + */ +TEST_F(DmodApiSignatureTest, IsBuiltinReturnsTrueForBuiltinSignature) +{ + static const char* builtinSig = DMOD_MAKE_BUILTIN_SIGNATURE(TestModule, 1/1, TestFunc); + static const char* dmodSig = DMOD_MAKE_SIGNATURE(TestModule, 1/1, TestFunc); + EXPECT_TRUE(Dmod_ApiSignature_IsBuiltin(builtinSig)); + EXPECT_FALSE(Dmod_ApiSignature_IsBuiltin(dmodSig)); +} From f7f398e03bef8dde389d0d0311c46f7866cf95d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:32:00 +0000 Subject: [PATCH 3/3] Add complex version string tests (dotted, multi-segment, multi-digit, mixed length) Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- .../public/tests_dmod_api_signature.cpp | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/tests/system/public/tests_dmod_api_signature.cpp b/tests/system/public/tests_dmod_api_signature.cpp index 8958c3e..915e1b2 100644 --- a/tests/system/public/tests_dmod_api_signature.cpp +++ b/tests/system/public/tests_dmod_api_signature.cpp @@ -434,3 +434,230 @@ TEST_F(DmodApiSignatureTest, IsBuiltinReturnsTrueForBuiltinSignature) EXPECT_TRUE(Dmod_ApiSignature_IsBuiltin(builtinSig)); EXPECT_FALSE(Dmod_ApiSignature_IsBuiltin(dmodSig)); } + +// =============================================================== +// Tests for complex / elaborate version strings +// +// Compatibility rules (derived from the implementation): +// API version: only the segment before the first '.' or '/' is compared. +// Module version: only the segment before the first '.' is compared. +// +// In DMOD_MAKE_SIGNATURE the VERSION token is stringified as-is, so +// 0.3/0.5 → version field "0.3/0.5" (API major "0", module major "0") +// 0.3.2/0.67.0 → "0.3.2/0.67.0" (API major "0", module major "0") +// 10/2 → "10/2" (API major "10", module major "2") +// 23.5/4.1 → "23.5/4.1" (API major "23", module major "4") +// =============================================================== + +// --------------------------------------------------------------- +// Group: two-component dotted version A.B / C.D +// --------------------------------------------------------------- + +/** + * @brief Identical two-component dotted versions must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDottedVersion_SameBoth) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different API minor (0.3 vs 0.7) with the same API major (0) must be + * compatible — the minor part after '.' is not compared. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDottedVersion_DifferentApiMinor) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.7/0.5, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different API major (0 vs 1) must be incompatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDottedVersion_DifferentApiMajorReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 1.3/0.5, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different module minor (0.5 vs 0.9) with the same module major (0) must + * be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDottedVersion_DifferentModuleMinor) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.9, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different module major (0 vs 1) must be incompatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleDottedVersion_DifferentModuleMajorReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3/1.5, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +// --------------------------------------------------------------- +// Group: multi-segment version A.B.C / D.E.F +// --------------------------------------------------------------- + +/** + * @brief Identical multi-segment versions must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_SameBoth) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different API middle and trailing segments (0.3.2 vs 0.9.1) with the + * same API major (0) must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_DifferentApiMinorSegments) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.9.1/0.67.0, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different API major (0 vs 1) in a multi-segment version must be + * incompatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_DifferentApiMajorReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 1.3.2/0.67.0, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different module minor segments (0.67.0 vs 0.99.5) with the same + * module major (0) must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_DifferentModuleMinorSegments) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.99.5, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different module major (0 vs 1) in a multi-segment version must be + * incompatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_DifferentModuleMajorReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/1.67.0, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different number of segments (0.3.2 vs 0.3) with the same API major + * must still be compatible — only the part before the first '.' matters. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiSegment_MixedSegmentCount) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3.2/0.67.0, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.67, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +// --------------------------------------------------------------- +// Group: multi-digit major versions and mixed-length strings +// --------------------------------------------------------------- + +/** + * @brief Two identical multi-digit versions (10/1) must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_SameBoth) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 10/1, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 10/1, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief API major 10 vs 1 must be incompatible: the comparison is character- + * based, so '1','0' (then '/') does not equal '1' (then '/'). + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_ApiMajor10vs1ReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 10/1, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 1/1, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Module major 10 vs 1 must be incompatible for the same reason. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_ModuleMajor10vs1ReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 1/10, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 1/1, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Large API minor digits (0.100 vs 0.999) with the same major (0) must + * be compatible — only the part before the first '.' is compared. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_LargeMinorApiVersions) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.100/0.200, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.999/0.888, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Multi-digit API major (23 vs 24) must be incompatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_ApiMajor23vs24ReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 23.5/0.1, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 24.5/0.1, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Same multi-digit API major (23) with different minors must be compatible. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_SameApiMajor23DifferentMinor) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 23.5/0.1, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 23.9/0.1, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Multi-digit module major (23 vs 24) must be incompatible even when + * the API version matches. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMultiDigit_ModuleMajor23vs24ReturnsFalse) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.5/23.1, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.5/24.1, Func); + EXPECT_FALSE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +} + +/** + * @brief Different minor lengths (0.3 vs 0.300) with the same major must be + * compatible — only the part before the first '.' is compared. + */ +TEST_F(DmodApiSignatureTest, AreCompatibleMixedMinorLength_LongVsShort) +{ + static const char* sig1 = DMOD_MAKE_SIGNATURE(Mod, 0.3/0.5, Func); + static const char* sig2 = DMOD_MAKE_SIGNATURE(Mod, 0.300/0.500, Func); + EXPECT_TRUE(Dmod_ApiSignature_AreCompatible(sig1, sig2)); +}