Files
create/tests/src/App/MappedElement.cpp
Weston Schmidt 87a4ce2e8d Add test causing crash and fix map sorting function. (#22889)
* Add test causing crash and fix map sorting function.

When opening a model from FreeCAD 0.7, the model would crash when I
changed some of the parameters.  This turned out to be due to the
ElementNameComparator::operator() not correctly sorting the items going
into the map, which caused the map to be invalid.

This change adds a test that represented the exact names causing the
crash as well as a fix for the problem.  Names are now sorted:

1. Empty names first
2. Identifier based names second.
3. Hex based names last.

Identifiers are sorted lexicographically for the name portion and
numerically for the number portion, smallest to largest.

Hex based names are sorted by the value of the hex number, smallest to
largest.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix lint issues.

* Add another test form to the mix.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-09-05 14:07:02 -05:00

318 lines
9.3 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
#include <gtest/gtest.h>
#include <utility>
#include <map>
#include <string>
#include <vector>
#include "App/IndexedName.h"
#include "App/MappedElement.h"
class MappedElementTest: public ::testing::Test
{
protected:
// void SetUp() override {}
// void TearDown() override {}
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
static Data::MappedElement givenMappedElement(const char* index, const char* name)
{
Data::IndexedName indexedName {index};
Data::MappedName mappedName {name};
return {indexedName, mappedName};
}
};
TEST_F(MappedElementTest, constructFromNameAndIndex)
{
// Arrange
Data::IndexedName indexedName {"EDGE1"};
Data::MappedName mappedName {"OTHER_NAME"};
// Act
Data::MappedElement mappedElement {indexedName, mappedName};
// Assert
EXPECT_EQ(mappedElement.index, indexedName);
EXPECT_EQ(mappedElement.name, mappedName);
}
TEST_F(MappedElementTest, moveConstructor)
{
// Arrange
auto originalMappedElement = givenMappedElement("EDGE1", "OTHER_NAME");
auto originalName = originalMappedElement.name;
auto originalIndex = originalMappedElement.index;
// Act
Data::MappedElement newMappedElement {std::move(originalMappedElement)};
// Assert
EXPECT_EQ(originalName, newMappedElement.name);
EXPECT_EQ(originalIndex, newMappedElement.index);
}
TEST_F(MappedElementTest, assignmentOperator)
{
// Arrange
auto mappedElementA = givenMappedElement("EDGE1", "OTHER_NAME");
auto mappedElementB = givenMappedElement("EDGE2", "ANOTHER_NAME");
EXPECT_NE(mappedElementA, mappedElementB); // Verify test setup
// Act
mappedElementA = mappedElementB;
// Assert
EXPECT_EQ(mappedElementA, mappedElementB);
}
TEST_F(MappedElementTest, moveAssignmentOperator)
{
// Arrange
auto mappedElementA = givenMappedElement("EDGE1", "OTHER_NAME");
auto mappedElementB = givenMappedElement("EDGE2", "ANOTHER_NAME");
EXPECT_NE(mappedElementA, mappedElementB); // Verify test setup
// Act
mappedElementA = std::move(mappedElementB);
// Assert
EXPECT_EQ(mappedElementA.name.toString(), "ANOTHER_NAME");
EXPECT_EQ(mappedElementA.index.toString(), "EDGE2");
}
TEST_F(MappedElementTest, equalityOperatorsWhenNotEqual)
{
// Arrange
auto mappedElementA = givenMappedElement("EDGE1", "OTHER_NAME");
auto mappedElementB = givenMappedElement("EDGE2", "ANOTHER_NAME");
// Act
bool aAndBAreEqual = mappedElementA == mappedElementB;
bool aAndBAreNotEqual = mappedElementA != mappedElementB;
// Assert
EXPECT_TRUE(aAndBAreNotEqual);
EXPECT_FALSE(aAndBAreEqual);
}
TEST_F(MappedElementTest, equalityOperatorsWhenEqual)
{
// Arrange
auto mappedElementA = givenMappedElement("EDGE1", "OTHER_NAME");
auto mappedElementB = givenMappedElement("EDGE1", "OTHER_NAME");
// Act
bool aAndBAreEqual = mappedElementA == mappedElementB;
bool aAndBAreNotEqual = mappedElementA != mappedElementB;
// Assert
EXPECT_FALSE(aAndBAreNotEqual);
EXPECT_TRUE(aAndBAreEqual);
}
TEST_F(MappedElementTest, lessThanOperator)
{
// Index is compared first, then mappedName
// Arrange
auto mappedElement1A = givenMappedElement("EDGE1", "A");
auto mappedElement1B = givenMappedElement("EDGE1", "B");
auto mappedElement2A = givenMappedElement("EDGE2", "A");
auto mappedElement2B = givenMappedElement("EDGE2", "B");
auto mappedElement2BDuplicate = givenMappedElement("EDGE2", "B");
// Act & Assert
EXPECT_TRUE(mappedElement1A < mappedElement1B);
EXPECT_TRUE(mappedElement1A < mappedElement2A);
EXPECT_TRUE(mappedElement1B < mappedElement2A);
EXPECT_FALSE(mappedElement2A < mappedElement1B);
EXPECT_FALSE(mappedElement2B < mappedElement2BDuplicate);
}
TEST_F(MappedElementTest, comparatorBothAreZeroSize)
{
// Arrange
Data::MappedName mappedName1 {""};
Data::MappedName mappedName2 {""};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_FALSE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorOneIsZeroSize)
{
// Arrange
Data::MappedName mappedName1 {""};
Data::MappedName mappedName2 {"#12345"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_TRUE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorBothStartWithHexDigitsThatDiffer)
{
// Arrange
Data::MappedName mappedName1 {"#fed;B"};
Data::MappedName mappedName2 {"#abcdef;A"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_TRUE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorBothStartWithTheSameHexDigits)
{
// Arrange
Data::MappedName mappedName1 {"#12345;B"};
Data::MappedName mappedName2 {"#12345;A"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_FALSE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorHexWithoutTerminator)
{
// Arrange
Data::MappedName mappedName1 {"#fed"};
Data::MappedName mappedName2 {"#abcdef"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_TRUE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorHexWithoutTerminatorSameLength)
{
// Arrange
Data::MappedName mappedName1 {"#fed"};
Data::MappedName mappedName2 {"#abc"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_FALSE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorNoHexDigitsLexicalCompare)
{
// Arrange
Data::MappedName mappedName1 {"A"};
Data::MappedName mappedName2 {"B"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_TRUE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorNoHexDigitsSameStringNumericCompare)
{
// Arrange
Data::MappedName mappedName1 {"Edge123456;"};
Data::MappedName mappedName2 {"Edge321;"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_FALSE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorIntegerWithoutTerminatorIsBroken)
{
// Arrange
Data::MappedName mappedName1 {"Edge123456"};
Data::MappedName mappedName2 {"Edge321"};
auto comp = Data::ElementNameComparator();
// Act & Assert
EXPECT_FALSE(comp(mappedName1, mappedName2));
}
TEST_F(MappedElementTest, comparatorThreeComplexHexNamesInMap)
{
// Arrange
Data::MappedName name1("#19c9:e;:U;FUS;:Hce4:7,E");
Data::MappedName name2("#1dadb:11;:L#1061a;FUS;:H:d,E");
Data::MappedName name3("#1dae6:8;:L#1dae4;FUS;:H:d,E");
std::map<Data::MappedName, int, Data::ElementNameComparator> testMap;
testMap[name1] = 1;
testMap[name2] = 2;
testMap[name3] = 3;
// Assert: map should have 3 unique keys
EXPECT_EQ(testMap.size(), 3);
// Collect keys in order
std::vector<std::string> keys;
for (const auto& kv : testMap) {
keys.push_back(kv.first.toString());
}
// Print for debug (optional)
// for (const auto& k : keys) std::cout << k << std::endl;
// The expected order depends on your comparator logic.
// If you want to check the exact order, set it here:
// (Replace with the correct expected order if needed)
std::vector<std::string> expectedOrder = {"#19c9:e;:U;FUS;:Hce4:7,E",
"#1dadb:11;:L#1061a;FUS;:H:d,E",
"#1dae6:8;:L#1dae4;FUS;:H:d,E"};
EXPECT_EQ(keys, expectedOrder);
}
TEST_F(MappedElementTest, comparatorLargerWorkedExampleWithMap)
{
// Arrange
Data::MappedName name0("Edge123;:U;FUS;:Hce4:7,E");
Data::MappedName name1("#1dad:e;:U;FUS;:Hce4:7,E");
Data::MappedName name2("#1dadb:11;:L#1061a;FUS;:H:d,E");
Data::MappedName name3("#1dae6:8;:L#1dae4;FUS;:H:d,E");
Data::MappedName name4("Edge999;;:L#1dae4;FUS;:H:d,E");
Data::MappedName name5("g4v2;SKT;:H1234,F;:H5678:2,E;:G0(g1;SKT;:H9012,E);XTR;:H3456:2,F");
std::map<Data::MappedName, int, Data::ElementNameComparator> testMap;
testMap[name0] = 1;
testMap[name1] = 2;
testMap[name2] = 3;
testMap[name3] = 4;
testMap[name0] = 5; // Duplicate, should not affect size
testMap[name1] = 6; // Duplicate, should not affect size
testMap[name4] = 7; // New entry
testMap[name4] = 8; // Duplicate, should not affect size
testMap[name2] = 9; // Duplicate, should not affect size
testMap[name3] = 10; // Duplicate, should not affect size
testMap[name5] = 11;
// Assert: map should have 5 unique keys
EXPECT_EQ(testMap.size(), 6);
// Collect keys in order
std::vector<std::string> keys;
for (const auto& kv : testMap) {
keys.push_back(kv.first.toString());
}
// Print for debug (optional)
// for (const auto& k : keys) std::cout << k << std::endl;
// The expected order depends on your comparator logic.
// If you want to check the exact order, set it here:
// (Replace with the correct expected order if needed)
std::vector<std::string> expectedOrder = {
"Edge123;:U;FUS;:Hce4:7,E",
"Edge999;;:L#1dae4;FUS;:H:d,E",
"g4v2;SKT;:H1234,F;:H5678:2,E;:G0(g1;SKT;:H9012,E);XTR;:H3456:2,F",
"#1dad:e;:U;FUS;:Hce4:7,E",
"#1dadb:11;:L#1061a;FUS;:H:d,E",
"#1dae6:8;:L#1dae4;FUS;:H:d,E"};
EXPECT_EQ(keys, expectedOrder);
}