"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
475 lines
14 KiB
C++
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);
|
|
}
|