[mapguide-commits] r10221 - branches/4.0/MgDev/Server/src/UnitTesting
svn_mapguide at osgeo.org
svn_mapguide at osgeo.org
Wed Oct 21 15:24:20 PDT 2026
Author: jng
Date: 2026-10-21 15:24:19 -0700 (Wed, 21 Oct 2026)
New Revision: 10221
Modified:
branches/4.0/MgDev/Server/src/UnitTesting/TestCoordinateSystem.cpp
Log:
#1835: Add regression test to verify this was indeed fixed. The fix landed in r7097
Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestCoordinateSystem.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestCoordinateSystem.cpp 2026-10-21 08:23:18 UTC (rev 10220)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestCoordinateSystem.cpp 2026-10-21 22:24:19 UTC (rev 10221)
@@ -21,6 +21,11 @@
#include "CatchHelperMacros.h"
#include "catch.hpp"
+#include <cstring>
+#include <fstream>
+#include <iterator>
+#include <vector>
+
#ifndef _WIN32
#define stricmp strcasecmp
@@ -433,6 +438,208 @@
&MgCoordinateSystemCatalog::GetCategoryDictionary>(L"Category", NULL, NULL);
}
+///----------------------------------------------------------------------------
+/// Test Case Description:
+///
+/// Regression test for #1835: adding a coordinate system to a category that
+/// already contains duplicate entries must not truncate that category.
+///
+/// CCoordinateSystemCategory::CopyFrom() used to rebuild a category by calling
+/// AddCoordinateSystem() for every name it enumerated. AddCoordinateSystem()
+/// throws on a duplicate, so the copy stopped at the first duplicate entry, and
+/// CCoordinateSystemCategoryDictionary::Modify() then wrote that truncated
+/// category back to the dictionary.
+///----------------------------------------------------------------------------
+
+//A category dictionary (.CSD) is a straight concatenation of fixed sized
+//records; see CSwrtCategory()/CSrdCategoryEx() in
+//Oem/CsMap/CsMapDev/Source/CS_category.c:
+// UINT32 magic
+// { char ctName[128]; UINT32 nameCnt; UINT32 allocCnt; char csName[24][allocCnt]; } *
+const size_t CategoryDictNameSize = 128;
+const size_t CategoryDictCsNameSize = 24;
+const size_t CategoryDictAllocCount = 12;
+const UINT32 CategoryDictMagic = 0x3372ED6C;
+
+static size_t CategoryDictStrLen(const unsigned char* buffer, size_t maxLength)
+{
+ size_t length = 0;
+ while (length < maxLength && buffer[length] != 0)
+ ++length;
+ return length;
+}
+
+static UINT32 ReadCategoryDictUInt32(const std::vector<unsigned char>& bytes, size_t offset)
+{
+ return (UINT32)bytes[offset]
+ | ((UINT32)bytes[offset + 1] << 8)
+ | ((UINT32)bytes[offset + 2] << 16)
+ | ((UINT32)bytes[offset + 3] << 24);
+}
+
+static void WriteCategoryDictUInt32(std::vector<unsigned char>& bytes, size_t offset, UINT32 value)
+{
+ bytes[offset] = (unsigned char)(value & 0xFF);
+ bytes[offset + 1] = (unsigned char)((value >> 8) & 0xFF);
+ bytes[offset + 2] = (unsigned char)((value >> 16) & 0xFF);
+ bytes[offset + 3] = (unsigned char)((value >> 24) & 0xFF);
+}
+
+static bool ReadCategoryDictFile(const STRING& file, std::vector<unsigned char>& bytes)
+{
+ std::string mbFile = MgUtil::WideCharToMultiByte(file);
+ std::ifstream stream(mbFile.c_str(), std::ios::in | std::ios::binary);
+ if (!stream.is_open())
+ return false;
+
+ bytes.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
+ return true;
+}
+
+static bool WriteCategoryDictFile(const STRING& file, const std::vector<unsigned char>& bytes)
+{
+ if (bytes.empty())
+ return false;
+
+ std::string mbFile = MgUtil::WideCharToMultiByte(file);
+ std::ofstream stream(mbFile.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+ if (!stream.is_open())
+ return false;
+
+ stream.write((const char*)&bytes[0], (std::streamsize)bytes.size());
+ return stream.good();
+}
+
+//Appends a copy of its own first CS name to the first non-empty category in the
+//given category dictionary, i.e. makes that category contain a duplicate entry.
+//Reports which category and CS name were used, plus the category's entry count
+//before the duplicate was added.
+static bool DuplicateCategoryEntry(const STRING& csdFile, STRING& categoryName, STRING& csName, UINT32& originalCount)
+{
+ std::vector<unsigned char> bytes;
+ if (!ReadCategoryDictFile(csdFile, bytes))
+ return false;
+
+ if (bytes.size() < 4 || ReadCategoryDictUInt32(bytes, 0) != CategoryDictMagic)
+ return false;
+
+ size_t offset = 4;
+ while (offset + CategoryDictNameSize + 8 <= bytes.size())
+ {
+ const unsigned char* mbCategory = &bytes[offset];
+ std::string mbCategoryName((const char*)mbCategory, CategoryDictStrLen(mbCategory, CategoryDictNameSize));
+ size_t countOffset = offset + CategoryDictNameSize;
+ UINT32 nameCount = ReadCategoryDictUInt32(bytes, countOffset);
+ UINT32 allocCount = ReadCategoryDictUInt32(bytes, countOffset + 4);
+ size_t namesOffset = countOffset + 8;
+
+ if (nameCount > allocCount || namesOffset + allocCount * CategoryDictCsNameSize > bytes.size())
+ return false;
+
+ if (nameCount > 0)
+ {
+ const unsigned char* mbFirst = &bytes[namesOffset];
+ std::string mbDuplicate((const char*)mbFirst, CategoryDictStrLen(mbFirst, CategoryDictCsNameSize));
+
+ if (nameCount == allocCount)
+ {
+ //grow the entry block the same way CS-Map's AddCsName() does
+ bytes.insert(bytes.begin() + (namesOffset + allocCount * CategoryDictCsNameSize),
+ CategoryDictAllocCount * CategoryDictCsNameSize, 0);
+ allocCount += (UINT32)CategoryDictAllocCount;
+ WriteCategoryDictUInt32(bytes, countOffset + 4, allocCount);
+ }
+
+ size_t newEntryOffset = namesOffset + nameCount * CategoryDictCsNameSize;
+ memset(&bytes[newEntryOffset], 0, CategoryDictCsNameSize);
+ memcpy(&bytes[newEntryOffset], mbDuplicate.c_str(), mbDuplicate.length());
+ WriteCategoryDictUInt32(bytes, countOffset, nameCount + 1);
+
+ categoryName = MgUtil::MultiByteToWideChar(mbCategoryName);
+ csName = MgUtil::MultiByteToWideChar(mbDuplicate);
+ originalCount = nameCount;
+
+ return WriteCategoryDictFile(csdFile, bytes);
+ }
+
+ offset = namesOffset + allocCount * CategoryDictCsNameSize;
+ }
+
+ return false;
+}
+
+TEST_CASE("AddCoordinateSystemToCategoryWithDuplicateEntries", "[CoordinateSystem]")
+{
+ try
+ {
+ MgCoordinateSystemFactory csFactory;
+ Ptr<MgCoordinateSystemCatalog> pCsCatalog = csFactory.GetCatalog();
+
+ //make sure the category dictionary is written into the system dictionary
+ if (!pCsCatalog->GetUserDictionaryDir().empty())
+ pCsCatalog->SetUserDictionaryDir(L"");
+
+ Ptr<MgCoordinateSystemCategoryDictionary> pCtDict = pCsCatalog->GetCategoryDictionary();
+ STRING csdFile = pCtDict->GetPath();
+ REQUIRE(MgFileUtil::IsFile(csdFile));
+
+ STRING categoryName;
+ STRING duplicateName;
+ UINT32 originalCount = 0;
+
+ {
+ //backup the current CSD file and automatically restore it after we are done
+ FileAutoBackup csdFileBackup(csdFile, L".UNIT_TEST_SAVE", true);
+
+ REQUIRE(DuplicateCategoryEntry(csdFile, categoryName, duplicateName, originalCount));
+ ACE_DEBUG((LM_INFO, ACE_TEXT("\nInjected duplicate %W into category %W\n"), duplicateName.c_str(), categoryName.c_str()));
+
+ pCtDict->SetFileName(pCtDict->GetFileName()); //force the Mg dictionary to re-read the file
+
+ Ptr<MgCoordinateSystemCategory> pCategory = pCtDict->GetCategory(categoryName);
+ REQUIRE(pCategory);
+
+ Ptr<MgStringCollection> pNamesBefore = pCategory->GetCoordinateSystems();
+ REQUIRE(pNamesBefore->GetCount() == (INT32)(originalCount + 1)); //the duplicate has been picked up
+ REQUIRE(pNamesBefore->Contains(duplicateName));
+
+ STRING newCsName = L"MG_UNIT_TEST_1835";
+ REQUIRE(!pNamesBefore->Contains(newCsName));
+
+ pCategory->AddCoordinateSystem(newCsName);
+ pCtDict->Modify(pCategory.p);
+
+ //now verify what's actually in the dictionary, not just the in-memory object
+ pCtDict->SetFileName(pCtDict->GetFileName());
+
+ pCategory = pCtDict->GetCategory(categoryName);
+ REQUIRE(pCategory);
+
+ Ptr<MgStringCollection> pNamesAfter = pCategory->GetCoordinateSystems();
+ REQUIRE(pNamesAfter->GetCount() == (INT32)(originalCount + 2));
+ REQUIRE(pNamesAfter->Contains(newCsName));
+
+ for (INT32 i = 0; i < pNamesBefore->GetCount(); i++)
+ {
+ REQUIRE(pNamesAfter->Contains(pNamesBefore->GetItem(i)));
+ }
+ }
+
+ //the CSD file has been restored - make sure the Mg dictionary sees that, too
+ pCtDict->SetFileName(pCtDict->GetFileName());
+ }
+ catch(MgException* e)
+ {
+ STRING message = e->GetDetails(TEST_LOCALE);
+ SAFE_RELEASE(e);
+ FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+ }
+ catch(...)
+ {
+ throw;
+ }
+}
+
/// (5) Update a geodetic transformation
TEST_CASE("UpdateGeodeticTransformations", "[CoordinateSystem]")
{
More information about the mapguide-commits
mailing list