Files
create/tests/src/Base/Reader.cpp
Markus Reitböck f0eca551b3 Base: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
should be considered an optimization, not a requirement. In particular, do not explicitly include a
precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
generated precompile header on the compiler command line instead. This is more portable across
the major compilers and is likely to be easier to maintain. It will also avoid warnings being
generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-14 09:47:01 +02:00

475 lines
14 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
#include <gtest/gtest.h>
#include <QString>
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#pragma warning(disable : 4305)
#endif
#include "Base/Exception.h"
#include "Base/Persistence.h"
#include "Base/Reader.h"
#include <array>
#include <filesystem>
#include <fstream>
#include <random>
#include <string>
#include <xercesc/util/PlatformUtils.hpp>
namespace fs = std::filesystem;
static std::string random_string(size_t length)
{
const std::string digits = "0123456789";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, static_cast<int>(digits.size()) - 1);
std::string result;
for (size_t i = 0; i < length; ++i) {
result += digits[dis(gen)];
}
return result;
}
class ReaderXML
{
public:
ReaderXML()
{
_tempDir = fs::temp_directory_path();
fs::path filename =
std::string("unit_test_Reader-") + random_string(4) + std::string(".xml");
_tempFile = _tempDir / filename;
}
~ReaderXML()
{
if (inputStream.is_open()) {
inputStream.close();
}
if (fs::exists(_tempFile)) {
fs::remove(_tempFile);
}
}
Base::XMLReader* Reader()
{
return _reader.get();
}
void givenDataAsXMLStream(const std::string& data)
{
auto stringData =
R"(<?xml version="1.0" encoding="UTF-8"?><document>)" + data + "</document>";
std::istringstream stream(stringData);
std::ofstream fileStream(_tempFile.string());
fileStream.write(stringData.data(), static_cast<std::streamsize>(stringData.length()));
fileStream.close();
inputStream.open(_tempFile.string());
_reader = std::make_unique<Base::XMLReader>(_tempFile.string().c_str(), inputStream);
}
private:
std::unique_ptr<Base::XMLReader> _reader;
fs::path _tempDir;
fs::path _tempFile;
std::ifstream inputStream;
};
class ReaderTest: public ::testing::Test
{
protected:
void SetUp() override
{
XERCES_CPP_NAMESPACE::XMLPlatformUtils::Initialize();
}
void TearDown() override
{}
};
TEST_F(ReaderTest, beginCharStreamNormal)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
// Act
auto& result = xml.Reader()->beginCharStream();
// Assert
EXPECT_TRUE(result.good());
}
TEST_F(ReaderTest, beginCharStreamOpenClose)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data id='12345' />");
xml.Reader()->readElement("data");
// Act
auto& result = xml.Reader()->beginCharStream(); // Not an error, even though there is no data
// Assert
EXPECT_TRUE(result.good());
}
TEST_F(ReaderTest, beginCharStreamAlreadyBegun)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
// Act & Assert
EXPECT_THROW(xml.Reader()->beginCharStream(), Base::XMLParseException); // NOLINT
}
TEST_F(ReaderTest, charStreamGood)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
// Act
auto& result = xml.Reader()->charStream();
// Assert
EXPECT_TRUE(result.good());
}
TEST_F(ReaderTest, charStreamBad)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
// Act & Assert
EXPECT_THROW(xml.Reader()->charStream(), Base::XMLParseException); // NOLINT
}
TEST_F(ReaderTest, endCharStreamGood)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
// Act & Assert
xml.Reader()->endCharStream(); // Does not throw
}
TEST_F(ReaderTest, endCharStreamBad)
{
// Arrange
ReaderXML xml;
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
xml.Reader()->readElement("data");
// Do not open the stream...
// Act & Assert
xml.Reader()->endCharStream(); // Does not throw, even with no open stream
}
TEST_F(ReaderTest, readDataSmallerThanBuffer)
{
// Arrange
constexpr size_t bufferSize {20};
std::string expectedData {"Test ASCII data"};
ReaderXML xml;
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
std::array<char, bufferSize> buffer {};
// Act
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
// Assert
EXPECT_STREQ(expectedData.c_str(), buffer.data());
EXPECT_EQ(expectedData.length(), bytesRead);
}
TEST_F(ReaderTest, readDataLargerThanBuffer)
{
// Arrange
constexpr size_t bufferSize {5};
std::string expectedData {"Test ASCII data"};
ReaderXML xml;
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
std::array<char, bufferSize> buffer {};
// Act
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
// Assert
for (size_t i = 0; i < bufferSize; ++i) {
EXPECT_EQ(expectedData[i], buffer.at(i));
}
EXPECT_EQ(bufferSize, bytesRead);
}
TEST_F(ReaderTest, readDataLargerThanBufferSecondRead)
{
// Arrange
constexpr size_t bufferSize {5};
std::string expectedData {"Test ASCII data"};
ReaderXML xml;
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream();
std::array<char, bufferSize> buffer {};
xml.Reader()->read(buffer.data(), bufferSize); // Read the first five bytes
// Act
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize); // Second five bytes
// Assert
for (size_t i = 0; i < bufferSize; ++i) {
EXPECT_EQ(expectedData[i + bufferSize], buffer.at(i));
}
EXPECT_EQ(bufferSize, bytesRead);
}
TEST_F(ReaderTest, readDataNotStarted)
{
// Arrange
constexpr size_t bufferSize {20};
std::string expectedData {"Test ASCII data"};
ReaderXML xml;
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
xml.Reader()->readElement("data");
std::array<char, bufferSize> buffer {};
// Act
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
// Assert
EXPECT_EQ(-1, bytesRead); // Because we didn't call beginCharStream
}
TEST_F(ReaderTest, readNextStartElement)
{
auto xmlBody = R"(
<node1 attr='1'>Node1</node1>
<node2 attr='2'>Node2</node2>
)";
ReaderXML xml;
xml.givenDataAsXMLStream(xmlBody);
// start of document
EXPECT_TRUE(xml.Reader()->isStartOfDocument());
xml.Reader()->readElement("document");
EXPECT_STREQ(xml.Reader()->localName(), "document");
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node1");
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "1");
xml.Reader()->readEndElement("node1");
EXPECT_TRUE(xml.Reader()->isEndOfElement());
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node2");
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "2");
xml.Reader()->readEndElement("node2");
EXPECT_TRUE(xml.Reader()->isEndOfElement());
xml.Reader()->readEndElement("document");
EXPECT_TRUE(xml.Reader()->isEndOfDocument());
}
TEST_F(ReaderTest, readNextStartEndElement)
{
// Arrange
enum class TimesIGoToBed
{
Late,
Later,
VeryLate,
FreeCADDevLate // https://user-images.githubusercontent.com/12400097/235325792-606bffd6-6607-4542-a7d9-a04f12120666.png
};
auto xmlBody = R"(
<node1 attr='1'/>
<node2 attr='2'/>
<node3 attr='3'/>
<node4 attr='1'/>
<node5 attr='0'/>
<node5b attr='0xFF'/>
<node6 attr='asdaf'/>
<node7 attr="const char* is faster :'("/>
<node8 attr='8'/>
<node9 attr='9'/>
<node10 attr='10'/>
<node11 attr='11'/>
)";
ReaderXML xml;
xml.givenDataAsXMLStream(xmlBody);
// start of document
EXPECT_TRUE(xml.Reader()->isStartOfDocument());
xml.Reader()->readElement("document");
EXPECT_STREQ(xml.Reader()->localName(), "document");
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node1");
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "1");
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node2");
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "2");
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node3");
EXPECT_EQ(xml.Reader()->getAttribute<TimesIGoToBed>("attr"), TimesIGoToBed::FreeCADDevLate);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node4");
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node5");
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), false);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node5b");
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node6");
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node7");
EXPECT_EQ(xml.Reader()->getAttribute<std::string>("attr"),
std::string("const char* is faster :'("));
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node8");
EXPECT_EQ(xml.Reader()->getAttribute<long>("attr"), 8);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node9");
EXPECT_EQ(xml.Reader()->getAttribute<unsigned long>("attr"), 9);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node10");
EXPECT_EQ(xml.Reader()->getAttribute<int>("attr"), 10);
// next element
EXPECT_TRUE(xml.Reader()->readNextElement());
EXPECT_STREQ(xml.Reader()->localName(), "node11");
EXPECT_EQ(xml.Reader()->getAttribute<QString>("attr"), QStringLiteral("11"));
EXPECT_FALSE(xml.Reader()->readNextElement());
EXPECT_TRUE(xml.Reader()->isEndOfDocument());
}
TEST_F(ReaderTest, charStreamBase64Encoded)
{
// Arrange
static constexpr size_t bufferSize {100};
std::array<char, bufferSize> buffer {};
ReaderXML xml;
xml.givenDataAsXMLStream("<data>RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n</data>");
xml.Reader()->readElement("data");
xml.Reader()->beginCharStream(Base::CharStreamFormat::Base64Encoded);
// Act
xml.Reader()->charStream().getline(buffer.data(), bufferSize);
xml.Reader()->endCharStream();
// Assert
// Conversion done using https://www.base64encode.org for testing purposes
EXPECT_EQ(std::string("FreeCAD rocks! 🪨🪨🪨"), std::string(buffer.data()));
}
TEST_F(ReaderTest, validDefaults)
{
// Arrange
enum class TimesIGoToBed
{
Late,
Later,
VeryLate,
FreeCADDevLate // https://user-images.githubusercontent.com/12400097/235325792-606bffd6-6607-4542-a7d9-a04f12120666.png
};
auto xmlBody = R"(
<node1 attr='1'/>
<node2 attr='2'/>
)";
ReaderXML xml;
xml.givenDataAsXMLStream(xmlBody);
// Act
const char* value2 = xml.Reader()->getAttribute<const char*>("missing", "expected value");
int value4 = xml.Reader()->getAttribute<long>("missing", -123);
unsigned value6 = xml.Reader()->getAttribute<unsigned long>("missing", 123);
double value8 = xml.Reader()->getAttribute<double>("missing", 1.234);
bool value12 = xml.Reader()->getAttribute<bool>("missing", 0);
bool value14 = xml.Reader()->getAttribute<bool>("missing", 1);
bool value16 = xml.Reader()->getAttribute<bool>("missing", -10);
bool value18 = xml.Reader()->getAttribute<bool>("missing", 10);
TimesIGoToBed value20 =
xml.Reader()->getAttribute<TimesIGoToBed>("missing", TimesIGoToBed::Late);
// Assert
EXPECT_THROW({ xml.Reader()->getAttribute<const char*>("missing"); }, Base::XMLBaseException);
EXPECT_EQ(value2, "expected value");
EXPECT_THROW({ xml.Reader()->getAttribute<long>("missing"); }, Base::XMLBaseException);
EXPECT_EQ(value4, -123);
EXPECT_THROW({ xml.Reader()->getAttribute<unsigned long>("missing"); }, Base::XMLBaseException);
EXPECT_EQ(value6, 123);
EXPECT_THROW({ xml.Reader()->getAttribute<double>("missing"); }, Base::XMLBaseException);
EXPECT_NEAR(value8, 1.234, 0.001);
EXPECT_THROW({ xml.Reader()->getAttribute<int>("missing"); }, Base::XMLBaseException);
EXPECT_NEAR(value8, 1.234, 0.001);
EXPECT_THROW({ xml.Reader()->getAttribute<bool>("missing"); }, Base::XMLBaseException);
EXPECT_EQ(value12, false);
EXPECT_EQ(value14, true);
EXPECT_EQ(value16, true);
EXPECT_EQ(value18, true);
EXPECT_THROW({ xml.Reader()->getAttribute<TimesIGoToBed>("missing"); }, Base::XMLBaseException);
EXPECT_EQ(value20, TimesIGoToBed::Late);
}
TEST_F(ReaderTest, validateXmlString)
{
std::string input = "abcde";
std::string output = input;
input.push_back(char(15));
output.push_back('_');
std::string result = Base::Persistence::validateXMLString(input);
EXPECT_EQ(output, result);
}