Merge pull request #8776 from chennes/toponamingIndexedName

App: Toponaming indexed name
This commit is contained in:
Chris Hennes
2023-03-14 19:21:19 -05:00
committed by GitHub
5 changed files with 1058 additions and 0 deletions

View File

@@ -261,6 +261,7 @@ SET(FreeCADApp_CPP_SRCS
ComplexGeoData.cpp
ComplexGeoDataPyImp.cpp
Enumeration.cpp
IndexedName.cpp
Material.cpp
MaterialPyImp.cpp
Metadata.cpp
@@ -277,6 +278,7 @@ SET(FreeCADApp_HPP_SRCS
ColorModel.h
ComplexGeoData.h
Enumeration.h
IndexedName.h
Material.h
Metadata.h
)

122
src/App/IndexedName.cpp Normal file
View File

@@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* Copyright (c) 2022 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* Copyright (c) 2023 FreeCAD Project Association *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
// NOLINTNEXTLINE
#include "PreCompiled.h"
#ifndef _PreComp_
# include <cstdlib>
# include <unordered_set>
#endif
#include "IndexedName.h"
using namespace Data;
/// Check whether the input character is an underscore or an ASCII letter a-Z or A-Z
inline bool isInvalidChar(char test)
{
return test != '_' && (test < 'a' || test > 'z' ) && (test < 'A' || test > 'Z');
}
/// Get the integer suffix of name. Returns a tuple of (suffix, suffixPosition). Calling code
/// should check to ensure that suffixPosition is not equal to nameLength (in which case there was no
/// suffix).
///
/// \param name The name to check
/// \param nameLength The length of the string in name
/// \returns An integer pair of the suffix itself and the position of that suffix in name
std::pair<int,int> getIntegerSuffix(const char *name, int nameLength)
{
int suffixPosition {nameLength - 1};
for (; suffixPosition >= 0; --suffixPosition) {
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
if (!isdigit(name[suffixPosition])) {
break;
}
}
++suffixPosition;
int suffix {0};
if (suffixPosition < nameLength) {
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
suffix = std::atoi(name + suffixPosition);
}
return std::make_pair(suffix, suffixPosition);
}
void IndexedName::set(
const char* name,
int length,
const std::vector<const char*>& allowedNames,
bool allowOthers)
{
// Storage for names that we weren't given external storage for
static std::unordered_set<ByteArray, ByteArrayHasher> NameSet;
if (length < 0) {
length = static_cast<int>(std::strlen(name));
}
// Name typically ends with an integer: find that integer
auto [suffix, suffixPosition] = getIntegerSuffix(name, length);
if (suffixPosition < length) {
this->index = suffix;
}
// Make sure that every character is either an ASCII letter (upper or lowercase), or an
// underscore. If any other character appears, reject the entire string.
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
if (std::any_of(name, name+suffixPosition, isInvalidChar)) {
this->type = "";
return;
}
// If a list of allowedNames was provided, see if our set name matches one of those allowedNames: if it
// does, reference that memory location and return.
for (const auto *typeName : allowedNames) {
if (std::strncmp(name, typeName, suffixPosition) == 0) {
this->type = typeName;
return;
}
}
// If the type was NOT in the list of allowedNames, but the caller has set the allowOthers flag to
// true, then add the new type to the static NameSet (if it is not already there).
if (allowOthers) {
auto res = NameSet.insert(ByteArray(QByteArray::fromRawData(name, suffixPosition)));
if (res.second /*The insert succeeded (the type was new)*/) {
// Make sure that the data in the set is a unique (unshared) copy of the text
res.first->ensureUnshared();
}
this->type = res.first->bytes.constData();
}
else {
// The passed-in type is not in the allowed list, and allowOthers was not true, so don't
// store the type
this->type = "";
}
}

338
src/App/IndexedName.h Normal file
View File

@@ -0,0 +1,338 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* Copyright (c) 2022 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* Copyright (c) 2023 FreeCAD Project Association *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef APP_INDEXEDNAME_H
#define APP_INDEXEDNAME_H
#include <cassert>
#include <cstring>
#include <ostream>
#include <string>
#include <vector>
#include <QByteArray>
#include <QHash>
#include "FCGlobal.h"
namespace Data
{
/// The IndexedName class provides a very memory-efficient data structure to hold a name and an index
/// value, and to perform various comparisons and validations of those values. The name must only
/// consist of upper- and lower-case ASCII characters and the underscore ('_') character. The index
/// must be a positive integer. The string representation of this IndexedName is the name followed by
/// the index, with no spaces between: an IndexedName may be constructed from this string. For
/// example "EDGE1" or "FACE345" might be the names of elements that use an IndexedName. If there is
/// then an "EDGE2", only a pointer to the original stored name "EDGE" is retained.
///
/// The memory efficiency of the class comes from re-using the same character storage for names that
/// match, while retaining their differing indices. This is achieved by either using user-provided
/// const char * names (provided as a list of typeNames and presumed to never be deallocated), or by
/// maintaining an internal list of names that have been used before, and can be re-used later.
class AppExport IndexedName {
public:
/// Construct from a name and an optional index. If the name contains an index it is read, but
/// is used as the index *only* if _index parameter is unset. If the _index parameter is given
/// it overrides any trailing integer in the name. Index must be positive, and name must contain
/// only ASCII letters and the underscore character. If these conditions are not met, name is
/// set to the empty string, and isNull() will return true.
///
/// \param name The new name - ASCII letters and underscores only, with optional integer suffix.
/// This memory will be copied into a new internal storage location and need not be persistent.
/// \param _index The new index - if provided, it overrides any suffix provided by name
explicit IndexedName(const char *name = nullptr, int _index = 0)
: index(0)
{
assert(_index >= 0);
if (!name) {
this->type = "";
}
else {
set(name);
if (_index > 0) {
this->index = _index;
}
}
}
/// Create an indexed name that is restricted to a list of preset type names. If it appears in
/// that list, only a pointer to the character storage in the list is retained: the memory
/// locations pointed at by the list must never be destroyed once they have been used to create
/// names. If allowOthers is true (the default) then a requested name that is not in the list
/// will be added to a static internal storage table, and its memory then re-used for later
/// objects with the same name. If allowOthers is false, then the name request is rejected, and
/// the name is treated as null.
///
/// \param name The new name - ASCII letters and underscores only, with optional integer suffix
/// \param allowedTypeNames A vector of allowed names. Storage locations must persist for the
/// entire run of the program.
/// \param allowOthers Whether a name not in allowedTypeNames is permitted. If true (the
/// default) then a name not in allowedTypeNames is added to a static internal storage vector
/// so that it can be re-used later without additional memory allocation.
IndexedName(const char *name,
const std::vector<const char*> & allowedTypeNames,
bool allowOthers=true) : type(""), index(0)
{
set(name, -1, allowedTypeNames, allowOthers);
}
/// Construct from a QByteArray, but explicitly making a copy of the name on its first
/// occurrence. If this is a name that has already been stored internally, no additional copy
/// is made.
///
/// \param data The QByteArray to copy the data from
explicit IndexedName(const QByteArray & data) : type(""), index(0)
{
set(data.constData(), data.size());
}
/// Given constant name and an index, re-use the existing memory for the name, not making a copy
/// of it, or scanning any existing storage for it. The name must never become invalid for the
/// lifetime of the object it names. This memory will never be re-used by another object.
///
/// \param name The name of the object. This memory is NOT copied and must be persistent.
/// \param index A positive, non-zero integer
/// \return An IndexedName with the given name and index, re-using the existing memory for name
static IndexedName fromConst(const char *name, int index) {
assert (index >= 0);
IndexedName res;
res.type = name;
res.index = index;
return res;
}
/// Given an existing std::string, *append* this name to it. If index is not zero, this will
/// include the index.
///
/// \param buffer A (possibly non-empty) string buffer to append the name to.
void appendToStringBuffer(std::string & buffer) const
{
buffer += this->type;
if (this->index > 0) {
buffer += std::to_string(this->index);
}
}
/// Create and return a new std::string with this name in it.
///
/// \return A newly-created string with the IndexedName in it (e.g. "EDGE42")
std::string toString() const
{
std::string result;
this->appendToStringBuffer(result);
return result;
}
/// An indexedName is represented as the simple concatenation of the name and its index, e.g.
/// "EDGE1" or "FACE42".
friend std::ostream & operator<<(std::ostream & stream, const IndexedName & indexedName)
{
stream << indexedName.type;
if (indexedName.index > 0) {
stream << indexedName.index;
}
return stream;
}
/// True only if both the name and index compare exactly equal.
bool operator==(const IndexedName & other) const
{
return this->index == other.index
&& (this->type == other.type
|| std::strcmp(this->type, other.type)==0);
}
/// Increments the index by the given offset. Does not affect the text part of the name.
IndexedName & operator+=(int offset)
{
this->index += offset;
assert(this->index >= 0);
return *this;
}
/// Pre-increment operator: increases the index of this element by one.
IndexedName & operator++()
{
++this->index;
return *this;
}
/// Pre-decrement operator: decreases the index of this element by one. Must not make the index
/// negative (only checked when compiled in debug mode).
IndexedName & operator--()
{
--this->index;
assert(this->index >= 0);
return *this;
}
/// True if either the name or the index compare not equal.
bool operator!=(const IndexedName & other) const
{
return !(this->operator==(other));
}
/// Equivalent to C++20's operator <=>
int compare(const IndexedName & other) const
{
int res = std::strcmp(this->type, other.type);
if (res != 0) {
return res;
}
if (this->index < other.index) {
return -1;
}
if (this->index > other.index) {
return 1;
}
return 0;
}
/// Provided to enable sorting operations: the comparison is first lexicographical for the text
/// element of the names, then numerical for the indices.
bool operator<(const IndexedName & other) const
{
return compare(other) < 0;
}
/// Allow direct memory access to the individual characters of the text portion of the name.
/// NOTE: input is not range-checked when compiled in release mode.
char operator[](int input) const
{
assert(input >= 0);
assert(input < static_cast<int>(std::strlen(this->type)));
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
return this->type[input];
}
/// Get a pointer to text part of the name - does NOT make a copy, returns direct memory access
const char * getType() const { return this->type; }
/// Get the numerical part of the name
int getIndex() const { return this->index; }
/// Set the numerical part of the name (note that there is no equivalent function to allow
/// changing the text part of the name, which is immutable once created).
///
/// \param input The new index. Must be a positive non-zero integer
void setIndex(int input) { assert(input>=0); this->index = input; }
/// A name is considered "null" if its text component is an empty string.
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
bool isNull() const { return this->type[0] == '\0'; }
/// Boolean conversion provides the opposite of isNull(), yielding true when the text part of
/// the name is NOT the empty string.
explicit operator bool() const { return !isNull(); }
protected:
/// Apply the IndexedName rules and either store the characters of a new type or a reference to
/// the characters in a type named in types, or stored statically within this function. If len
/// is not set, or set to -1 (the default), then the provided string in name is scanned for its
/// length using strlen (e.g. it must be null-terminated).
///
/// \param name The new name. If necessary a copy is made, this char * need not be persistent
/// \param length The length of name
/// \param allowedNames A vector of storage locations of allowed names. These storage locations
/// must be persistent for the duration of the program run.
/// \param allowOthers If true (the default), then if name is not in allowedNames it is allowed,
/// and it is added to internal storage (making a copy of the name if this is its first
/// occurrence).
void set(const char *name,
int length = -1,
const std::vector<const char *> & allowedNames = {},
bool allowOthers = true);
private:
const char * type;
int index;
};
/// A thin wrapper around a QByteArray providing the ability to force a copy of the data at any
/// time, even if it isn't being written to. The standard assignment operator for this class *does*
/// make a copy of the data, unlike the standard assignment operator for QByteArray.
struct ByteArray
{
explicit ByteArray(QByteArray other)
:bytes(std::move(other))
{}
ByteArray(const ByteArray& other) = default;
ByteArray(ByteArray&& other) noexcept
:bytes(std::move(other.bytes))
{}
~ByteArray() = default;
/// Guarantee that the stored QByteArray does not share its memory with another instance.
void ensureUnshared() const
{
QByteArray copy;
copy.append(bytes.constData(), bytes.size());
bytes = copy;
}
bool operator==(const ByteArray& other) const {
return bytes == other.bytes;
}
ByteArray &operator=(const ByteArray & other) {
bytes.clear();
bytes.append(other.bytes.constData(), other.bytes.size());
return *this;
}
ByteArray &operator= (ByteArray&& other) noexcept
{
bytes = std::move(other.bytes);
return *this;
}
mutable QByteArray bytes;
};
struct ByteArrayHasher
{
std::size_t operator()(const ByteArray& bytes) const
{
return qHash(bytes.bytes);
}
std::size_t operator()(const QByteArray& bytes) const
{
return qHash(bytes);
}
};
}
#endif // APP_INDEXEDNAME_H

View File

@@ -3,6 +3,7 @@ target_sources(
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Branding.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Expression.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IndexedName.cpp
${CMAKE_CURRENT_SOURCE_DIR}/License.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Metadata.cpp
)

View File

@@ -0,0 +1,595 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "gtest/gtest.h"
#include "App/IndexedName.h"
#include <sstream>
// NOLINTBEGIN(readability-magic-numbers)
class IndexedNameTest : public ::testing::Test {
protected:
// void SetUp() override {}
// void TearDown() override {}
// Create and return a list of invalid IndexedNames
static std::vector<Data::IndexedName> givenInvalidIndexedNames() {
return std::vector<Data::IndexedName> {
Data::IndexedName(),
Data::IndexedName("",1),
Data::IndexedName("INVALID42NAME",1),
Data::IndexedName(".EDGE",1)
};
}
// Create and return a list of valid IndexedNames
static std::vector<Data::IndexedName> givenValidIndexedNames() {
return std::vector<Data::IndexedName> {
Data::IndexedName("NAME"),
Data::IndexedName("NAME1"),
Data::IndexedName("NAME",1),
Data::IndexedName("NAME_WITH_UNDERSCORES12345")
};
}
// An arbitrary list of C strings used for testing some types of construction
// NOLINTNEXTLINE cppcoreguidelines-non-private-member-variables-in-classes
std::vector<const char *> allowedTypes {
"VERTEX",
"EDGE",
"FACE",
"WIRE"
};
};
TEST_F(IndexedNameTest, defaultConstruction)
{
// Act
auto indexedName = Data::IndexedName();
// Assert
EXPECT_STREQ(indexedName.getType(), "");
EXPECT_EQ(indexedName.getIndex(), 0);
}
TEST_F(IndexedNameTest, nameOnlyConstruction)
{
// Act
auto indexedName = Data::IndexedName("TestName");
// Assert
EXPECT_STREQ(indexedName.getType(), "TestName");
EXPECT_EQ(indexedName.getIndex(), 0);
}
TEST_F(IndexedNameTest, nameAndIndexConstruction)
{
// Arrange
const int testIndex {42};
// Act
auto indexedName = Data::IndexedName("TestName", testIndex);
// Assert
EXPECT_STREQ(indexedName.getType(), "TestName");
EXPECT_EQ(indexedName.getIndex(), testIndex);
}
TEST_F(IndexedNameTest, nameAndIndexConstructionWithOverride)
{
// Arrange
const int testIndex {42};
// Act
auto indexedName = Data::IndexedName("TestName17", testIndex);
// Assert
EXPECT_STREQ(indexedName.getType(), "TestName");
EXPECT_EQ(indexedName.getIndex(), testIndex);
}
// Names must only contain ASCII letters and underscores (but may end with a number)
TEST_F(IndexedNameTest, constructionInvalidCharInName)
{
// Arrange
constexpr int lastASCIICode{127};
std::vector<char> illegalCharacters = {};
for (int code = 1; code <= lastASCIICode; ++code) {
if ((std::isalnum(code) == 0) && code != '_') {
illegalCharacters.push_back(char(code));
}
}
for (auto illegalChar : illegalCharacters) {
std::string testName {"TestName"};
testName += illegalChar;
// Act
auto indexedName = Data::IndexedName(testName.c_str(), 1);
// Assert
EXPECT_STREQ(indexedName.getType(), "") << "Expected empty name when given " << testName;
}
}
// Names must not contain numbers in the middle:
TEST_F(IndexedNameTest, constructionNumberInName)
{
// Arrange
const int testIndex {42};
std::string testName;
testName += "Test" + std::to_string(testIndex) + "Name";
// Act
auto indexedName = Data::IndexedName(testName.c_str(), testIndex);
// Assert
EXPECT_STREQ(indexedName.getType(), "");
}
TEST_F(IndexedNameTest, nameAndTypeListConstructionWithoutAllowOthers)
{
// Act
auto indexedName = Data::IndexedName("EDGE19", allowedTypes, false);
// Assert
EXPECT_STREQ(indexedName.getType(), "EDGE");
EXPECT_EQ(indexedName.getIndex(), 19);
// Act
indexedName = Data::IndexedName("EDGES_ARE_REALLY_GREAT19", allowedTypes, false);
// Assert
EXPECT_STREQ(indexedName.getType(), "");
EXPECT_EQ(indexedName.getIndex(), 19);
// Act
indexedName = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, false);
// Assert
EXPECT_STREQ(indexedName.getType(), "");
}
TEST_F(IndexedNameTest, nameAndTypeListConstructionWithAllowOthers)
{
// Act
auto indexedName = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, true);
// Assert
EXPECT_STREQ(indexedName.getType(), "NOT_IN_THE_LIST");
EXPECT_EQ(indexedName.getIndex(), 42);
}
// Check that the same memory location is used for two names that are not in the allowedTypes list
TEST_F(IndexedNameTest, nameAndTypeListConstructionReusedMemoryCheck)
{
// Arrange
auto indexedName1 = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, true);
auto indexedName2 = Data::IndexedName("NOT_IN_THE_LIST43", allowedTypes, true);
// Act & Assert
EXPECT_EQ(indexedName1.getType(), indexedName2.getType());
}
TEST_F(IndexedNameTest, byteArrayConstruction)
{
// Arrange
QByteArray qba{"EDGE42"};
// Act
auto indexedName = Data::IndexedName(qba);
// Assert
EXPECT_STREQ(indexedName.getType(), "EDGE");
EXPECT_EQ(indexedName.getIndex(), 42);
}
TEST_F(IndexedNameTest, copyConstruction)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
// Act
auto indexedNameCopy {indexedName};
// Assert
EXPECT_EQ(indexedName, indexedNameCopy);
}
TEST_F(IndexedNameTest, streamInsertionOperator)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
std::stringstream ss;
// Act
ss << indexedName;
// Assert
EXPECT_EQ(ss.str(), std::string{"EDGE42"});
}
TEST_F(IndexedNameTest, compoundAssignmentOperator)
{
// NOTE: Only += is defined for this class
// Arrange
constexpr int base{42};
constexpr int offset{10};
auto indexedName = Data::IndexedName("EDGE",base);
// Act
indexedName += offset;
// Assert
EXPECT_EQ(indexedName.getIndex(), 52);
}
TEST_F(IndexedNameTest, preincrementOperator)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
// Act
++indexedName;
// Assert
EXPECT_EQ(indexedName.getIndex(), 43);
}
TEST_F(IndexedNameTest, predecrementOperator)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
// Act
--indexedName;
// Assert
EXPECT_EQ(indexedName.getIndex(), 41);
}
TEST_F(IndexedNameTest, comparisonOperators)
{
// Arrange
auto indexedName1 = Data::IndexedName("EDGE42");
auto indexedName2 = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_EQ(indexedName1.compare(indexedName2), 0);
EXPECT_TRUE(indexedName1 == indexedName2);
EXPECT_FALSE(indexedName1 != indexedName2);
EXPECT_FALSE(indexedName1 < indexedName2);
// Arrange
auto indexedName3 = Data::IndexedName("EDGE42");
auto indexedName4 = Data::IndexedName("FACE42");
// Act & Assert
EXPECT_EQ(indexedName3.compare(indexedName4), -1);
EXPECT_FALSE(indexedName3 == indexedName4);
EXPECT_TRUE(indexedName3 != indexedName4);
EXPECT_TRUE(indexedName3 < indexedName4);
// Arrange
auto indexedName5 = Data::IndexedName("FACE42");
auto indexedName6 = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_EQ(indexedName5.compare(indexedName6), 1);
EXPECT_FALSE(indexedName5 == indexedName6);
EXPECT_TRUE(indexedName5 != indexedName6);
EXPECT_FALSE(indexedName5 < indexedName6);
// Arrange
auto indexedName7 = Data::IndexedName("EDGE41");
auto indexedName8 = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_EQ(indexedName7.compare(indexedName8), -1);
EXPECT_FALSE(indexedName7 == indexedName8);
EXPECT_TRUE(indexedName7 != indexedName8);
EXPECT_TRUE(indexedName7 < indexedName8);
// Arrange
auto indexedName9 = Data::IndexedName("EDGE43");
auto indexedName10 = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_EQ(indexedName9.compare(indexedName10), 1);
EXPECT_FALSE(indexedName9 == indexedName10);
EXPECT_TRUE(indexedName9 != indexedName10);
EXPECT_FALSE(indexedName9 < indexedName10);
}
TEST_F(IndexedNameTest, subscriptOperator)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_EQ(indexedName[0], 'E');
EXPECT_EQ(indexedName[1], 'D');
EXPECT_EQ(indexedName[2], 'G');
EXPECT_EQ(indexedName[3], 'E');
}
TEST_F(IndexedNameTest, getType)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
// Act & Assert
EXPECT_STREQ(indexedName.getType(), "EDGE");
}
TEST_F(IndexedNameTest, setIndex)
{
// Arrange
auto indexedName = Data::IndexedName("EDGE42");
EXPECT_EQ(indexedName.getIndex(), 42);
// Act
indexedName.setIndex(1);
// Assert
EXPECT_EQ(indexedName.getIndex(), 1);
}
TEST_F(IndexedNameTest, isNullTrue)
{
// Arrange
auto invalidNames = givenInvalidIndexedNames();
for (const auto &name : invalidNames) {
// Act & Assert
EXPECT_TRUE(name.isNull());
}
}
TEST_F(IndexedNameTest, isNullFalse)
{
// Arrange
auto validNames = givenValidIndexedNames();
for (const auto &name : validNames) {
// Act & Assert
EXPECT_FALSE(name.isNull());
}
}
TEST_F(IndexedNameTest, booleanConversionFalse)
{
// Arrange
auto invalidNames = givenInvalidIndexedNames();
for (const auto &name : invalidNames) {
// Act & Assert
EXPECT_FALSE(static_cast<bool>(name));
}
// Usage example:
auto indexedName = Data::IndexedName(".EDGE",1); // Invalid name
if (indexedName) {
FAIL() << "indexedName as a boolean should have been false for an invalid name";
}
}
TEST_F(IndexedNameTest, booleanConversionTrue)
{
// Arrange
auto validNames = givenValidIndexedNames();
for (const auto& name : validNames) {
// Act & Assert
EXPECT_TRUE(static_cast<bool>(name));
}
}
TEST_F(IndexedNameTest, fromConst)
{
// Arrange
const int testIndex {42};
// Act
auto indexedName = Data::IndexedName::fromConst("TestName", testIndex);
// Assert
EXPECT_STREQ(indexedName.getType(), "TestName");
EXPECT_EQ(indexedName.getIndex(), testIndex);
}
TEST_F(IndexedNameTest, appendToStringBufferEmptyBuffer)
{
// Arrange
std::string bufferStartedEmpty;
Data::IndexedName testName("TEST_NAME", 1);
// Act
testName.appendToStringBuffer(bufferStartedEmpty);
// Assert
EXPECT_EQ(bufferStartedEmpty, "TEST_NAME1");
}
TEST_F(IndexedNameTest, appendToStringBufferNonEmptyBuffer)
{
// Arrange
std::string bufferWithData {"DATA"};
Data::IndexedName testName("TEST_NAME", 1);
// Act
testName.appendToStringBuffer(bufferWithData);
// Assert
EXPECT_EQ(bufferWithData, "DATATEST_NAME1");
}
TEST_F(IndexedNameTest, appendToStringBufferZeroIndex)
{
// Arrange
std::string bufferStartedEmpty;
Data::IndexedName testName("TEST_NAME", 0);
// Act
testName.appendToStringBuffer(bufferStartedEmpty);
// Assert
EXPECT_EQ(bufferStartedEmpty, "TEST_NAME");
}
TEST_F(IndexedNameTest, toString)
{
// Arrange
Data::IndexedName testName("TEST_NAME", 1);
// Act
auto result = testName.toString();
// Assert
EXPECT_EQ(result, "TEST_NAME1");
}
TEST_F(IndexedNameTest, toStringNoIndex)
{
// Arrange
Data::IndexedName testName("TEST_NAME", 0);
// Act
auto result = testName.toString();
// Assert
EXPECT_EQ(result, "TEST_NAME");
}
TEST_F(IndexedNameTest, assignmentOperator)
{
// Arrange
const int testIndex1 {42};
const int testIndex2 {24};
auto indexedName1 = Data::IndexedName::fromConst("TestName", testIndex1);
auto indexedName2 = Data::IndexedName::fromConst("TestName2", testIndex2);
EXPECT_NE(indexedName1, indexedName2); // Ensure the test is set up correctly
// Act
indexedName1 = indexedName2;
// Assert
EXPECT_EQ(indexedName1, indexedName2);
}
class ByteArrayTest : public ::testing::Test {
protected:
// void SetUp() override {}
// void TearDown() override {}
};
TEST_F(ByteArrayTest, QByteArrayConstruction)
{
// Arrange
QByteArray testQBA("Data in a QByteArray");
// Act
Data::ByteArray testByteArray (testQBA);
// Assert
EXPECT_EQ(testQBA, testByteArray.bytes);
}
TEST_F(ByteArrayTest, CopyConstruction)
{
// Arrange
QByteArray testQBA("Data in a QByteArray");
Data::ByteArray originalByteArray (testQBA);
// Act
// NOLINTNEXTLINE performance-unnecessary-copy-initialization
Data::ByteArray copiedByteArray (originalByteArray);
// Assert
EXPECT_EQ(originalByteArray, copiedByteArray);
}
TEST_F(ByteArrayTest, MoveConstruction)
{
// Arrange
QByteArray testQBA("Data in a QByteArray");
Data::ByteArray originalByteArray (testQBA);
const auto *originalDataLocation = originalByteArray.bytes.constData();
// Act
Data::ByteArray copiedByteArray (std::move(originalByteArray));
// Assert
EXPECT_EQ(testQBA, copiedByteArray.bytes);
EXPECT_EQ(originalDataLocation, copiedByteArray.bytes.constData());
}
TEST_F(ByteArrayTest, ensureUnshared)
{
// Arrange
QByteArray testQBA("Data in a QByteArray");
Data::ByteArray originalByteArray (testQBA);
const auto *originalDataLocation = originalByteArray.bytes.constData();
Data::ByteArray copiedByteArray (originalByteArray);
// Act
copiedByteArray.ensureUnshared();
// Assert
EXPECT_EQ(testQBA, copiedByteArray.bytes);
EXPECT_NE(originalDataLocation, copiedByteArray.bytes.constData());
}
TEST_F(ByteArrayTest, equalityOperator)
{
// Arrange
QByteArray testQBA1("Data in a QByteArray");
QByteArray testQBA2("Data in a QByteArray");
QByteArray testQBA3("Not the same data in a QByteArray");
Data::ByteArray byteArray1 (testQBA1);
Data::ByteArray byteArray2 (testQBA2);
Data::ByteArray byteArray3 (testQBA3);
// Act & Assert
EXPECT_TRUE(byteArray1 == byteArray2);
EXPECT_FALSE(byteArray1 == byteArray3);
}
TEST_F(ByteArrayTest, assignmentOperator)
{
// Arrange
QByteArray testQBA1("Data in a QByteArray");
QByteArray testQBA2("Different data in a QByteArray");
Data::ByteArray originalByteArray (testQBA1);
Data::ByteArray newByteArray (testQBA2);
ASSERT_FALSE(originalByteArray == newByteArray);
// Act
newByteArray = originalByteArray;
// Assert
EXPECT_TRUE(originalByteArray == newByteArray);
}
TEST_F(ByteArrayTest, moveAssignmentOperator)
{
// Arrange
QByteArray testQBA1("Data in a QByteArray");
QByteArray testQBA2("Different data in a QByteArray");
Data::ByteArray originalByteArray (testQBA1);
const auto *originalByteArrayLocation = originalByteArray.bytes.constData();
Data::ByteArray newByteArray (testQBA2);
ASSERT_FALSE(originalByteArray == newByteArray);
// Act
newByteArray = std::move(originalByteArray);
// Assert
EXPECT_EQ(originalByteArrayLocation, newByteArray.bytes.constData());
}
// NOLINTEND(readability-magic-numbers)