Merge branch 'Zbyl-string-functions-semantics'

This commit is contained in:
Wojtek Figat
2021-08-16 10:07:17 +02:00
12 changed files with 18476 additions and 25 deletions

View File

@@ -226,7 +226,9 @@ bool String::IsANSI() const
bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) const
{
if (prefix.IsEmpty() || prefix.Length() > Length())
if (prefix.IsEmpty())
return true;
if (prefix.Length() > Length())
return false;
if (searchCase == StringSearchCase::IgnoreCase)
return !StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length());
@@ -235,7 +237,9 @@ bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) c
bool String::EndsWith(const StringView& suffix, StringSearchCase searchCase) const
{
if (suffix.IsEmpty() || suffix.Length() > Length())
if (suffix.IsEmpty())
return true;
if (suffix.Length() > Length())
return false;
if (searchCase == StringSearchCase::IgnoreCase)
return !StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix);

View File

@@ -65,10 +65,11 @@ public:
/// <summary>
/// Lexicographically tests how this string compares to the other given string.
/// In case sensitive mode 'A' is less than 'a'.
/// </summary>
/// <param name="str">The another string test against.</param>
/// <param name="searchCase">The case sensitivity mode.</param>
/// <returns>0 if equal, -1 if less than, 1 if greater than.</returns>
/// <returns>0 if equal, negative number if less than, positive number if greater than.</returns>
int32 Compare(const StringBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
if (searchCase == StringSearchCase::CaseSensitive)
@@ -352,7 +353,7 @@ public:
bool StartsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
const int32 length = Length();
if (searchCase == StringSearchCase::IgnoreCase)
if (searchCase == StringSearchCase::CaseSensitive)
return length > 0 && _data[0] == c;
return length > 0 && StringUtils::ToLower(_data[0]) == StringUtils::ToLower(c);
}
@@ -360,14 +361,16 @@ public:
bool EndsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
const int32 length = Length();
if (searchCase == StringSearchCase::IgnoreCase)
if (searchCase == StringSearchCase::CaseSensitive)
return length > 0 && _data[length - 1] == c;
return length > 0 && StringUtils::ToLower(_data[length - 1]) == StringUtils::ToLower(c);
}
bool StartsWith(const StringBase& prefix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
if (prefix.IsEmpty() || Length() < prefix.Length())
if (prefix.IsEmpty())
return true;
if (Length() < prefix.Length())
return false;
if (searchCase == StringSearchCase::IgnoreCase)
return StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()) == 0;
@@ -376,7 +379,9 @@ public:
bool EndsWith(const StringBase& suffix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
if (suffix.IsEmpty() || Length() < suffix.Length())
if (suffix.IsEmpty())
return true;
if (Length() < suffix.Length())
return false;
if (searchCase == StringSearchCase::IgnoreCase)
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
@@ -429,22 +434,19 @@ public:
/// <summary>
/// Replaces all occurences of searchText within current string with replacementText.
/// </summary>
/// <param name="searchText">String to search for. If empty or null no replacements are done.</param>
/// <param name="searchTextLength">Length of searchText.</param>
/// <param name="searchText">String to search for.</param>
/// <param name="searchTextLength">Length of searchText. Must be greater than zero.</param>
/// <param name="replacementText">String to replace with. Null is treated as empty string.</param>
/// <param name="replacementTextLength">Length of replacementText.</param>
/// <returns>Number of replacements made. (In case-sensitive mode if search text and replacement text are equal no replacements are done, and zero is returned.)</returns>
/// <returns>Number of replacements made (in other words number of occurences of searchText).</returns>
int32 Replace(const T* searchText, int32 searchTextLength, const T* replacementText, int32 replacementTextLength, StringSearchCase searchCase = StringSearchCase::CaseSensitive)
{
if (!HasChars())
return 0;
if (searchTextLength == 0)
return 0;
// If we are doing case sensitive search and replacement text is equal to search text, we do nothing.
if ((searchCase == StringSearchCase::CaseSensitive) && (searchTextLength == replacementTextLength) && (StringUtils::Compare(searchText, replacementText) == 0))
{
ASSERT(false); // Empty search text never makes sense, and is always sign of a bug in calling code.
return 0;
}

View File

@@ -54,21 +54,23 @@ public:
/// <summary>
/// Lexicographically tests how this string compares to the other given string.
/// In case sensitive mode 'A' is less than 'a'.
/// </summary>
/// <param name="str">The another string test against.</param>
/// <param name="searchCase">The case sensitivity mode.</param>
/// <returns>0 if equal, -1 if less than, 1 if greater than.</returns>
/// <returns>0 if equal, negative number if less than, positive number if greater than.</returns>
int32 Compare(const StringViewBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
const int32 lengthDiff = Length() - str.Length();
if (lengthDiff != 0)
return lengthDiff;
if (Length() == 0)
const bool thisIsShorter = Length() < str.Length();
const int32 minLength = thisIsShorter ? Length() : str.Length();
const int32 prefixCompare = (searchCase == StringSearchCase::CaseSensitive)
? StringUtils::Compare(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength)
: StringUtils::CompareIgnoreCase(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength);
if (prefixCompare != 0)
return prefixCompare;
if (Length() == str.Length())
return 0;
// We know here that both this StringView and str are not empty, and therefore Get() below are valid.
if (searchCase == StringSearchCase::CaseSensitive)
return StringUtils::Compare(this->Get(), str.Get(), Length());
return StringUtils::CompareIgnoreCase(this->Get(), str.Get(), Length());
return thisIsShorter ? -1 : 1;
}
public:

View File

@@ -0,0 +1,334 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("String Replace works") {
SECTION("Char, case sensitive") {
String str("hello HELLO");
CHECK(str.Replace('l', 'x', StringSearchCase::CaseSensitive) == 2);
CHECK(str == String("hexxo HELLO"));
}
SECTION("Char, ignore case") {
String str("hello HELLO");
CHECK(str.Replace('l', 'x', StringSearchCase::IgnoreCase) == 4);
CHECK(str == String("hexxo HExxO"));
}
SECTION("case sensitive") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT("hi"), StringSearchCase::CaseSensitive) == 2);
CHECK(str == String("hi HELLO this is me saying hi"));
}
SECTION("ignore case") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT("hi"), StringSearchCase::IgnoreCase) == 3);
CHECK(str == String("hi hi this is me saying hi"));
}
SECTION("case sensitive, search and replace texts identical") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT("hello"), StringSearchCase::CaseSensitive) == 2);
CHECK(str == String("hello HELLO this is me saying hello"));
}
SECTION("ignore case, search and replace texts identical") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT("hello"), StringSearchCase::IgnoreCase) == 3);
CHECK(str == String("hello hello this is me saying hello"));
}
SECTION("case sensitive, replace text empty") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT(""), StringSearchCase::CaseSensitive) == 2);
CHECK(str == String(" HELLO this is me saying "));
}
SECTION("ignore case, replace text empty") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("hello"), TEXT(""), StringSearchCase::IgnoreCase) == 3);
CHECK(str == String(" this is me saying "));
}
SECTION("no finds") {
String str("hello HELLO this is me saying hello");
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::CaseSensitive) == 0);
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::IgnoreCase) == 0);
CHECK(str == String("hello HELLO this is me saying hello"));
}
SECTION("empty input") {
String str("");
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::CaseSensitive) == 0);
CHECK(str.Replace(TEXT("bye"), TEXT("hi"), StringSearchCase::IgnoreCase) == 0);
CHECK(str == String(""));
}
}
TEST_CASE("String Starts/EndsWith works") {
SECTION("StartsWith, case sensitive") {
SECTION("Char") {
CHECK(String("").StartsWith('h', StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith('h', StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith('H', StringSearchCase::CaseSensitive) == false);
}
SECTION("String") {
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith(String(TEXT("HELLO")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(String(TEXT("")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("").StartsWith(String(TEXT("x")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(String(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
}
SECTION("StringView") {
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("HELLO")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(StringView(), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("").StartsWith(StringView(TEXT("x")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
}
}
SECTION("StartsWith, ignore case") {
SECTION("Char") {
CHECK(String("").StartsWith('h', StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").StartsWith('h', StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith('H', StringSearchCase::IgnoreCase) == true);
}
SECTION("String") {
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(String(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(String(TEXT("")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("").StartsWith(String(TEXT("x")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").StartsWith(String(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").StartsWith(String(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
}
SECTION("StringView") {
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(StringView(), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("").StartsWith(StringView(TEXT("x")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").StartsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
}
}
SECTION("EndsWith, case sensitive") {
SECTION("Char") {
CHECK(String("").EndsWith('h', StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith('O', StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith('o', StringSearchCase::CaseSensitive) == false);
}
SECTION("String") {
CHECK(String("hello HELLO").EndsWith(String(TEXT("HELLO")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(String(TEXT("")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("").EndsWith(String(TEXT("x")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(String(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
}
SECTION("StringView") {
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("HELLO")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(StringView(), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("")), StringSearchCase::CaseSensitive) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("").EndsWith(StringView(TEXT("x")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::CaseSensitive) == false);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::CaseSensitive) == false);
}
}
SECTION("EndsWith, ignore case") {
SECTION("Char") {
CHECK(String("").EndsWith('h', StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").EndsWith('O', StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith('o', StringSearchCase::IgnoreCase) == true);
}
SECTION("String") {
CHECK(String("hello HELLO").EndsWith(String(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(String(TEXT("")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("").EndsWith(String(TEXT("x")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").EndsWith(String(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").EndsWith(String(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
}
SECTION("StringView") {
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("HELLO")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(StringView(), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("")), StringSearchCase::IgnoreCase) == true);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("").EndsWith(StringView(TEXT("x")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("hello HELLOx")), StringSearchCase::IgnoreCase) == false);
CHECK(String("hello HELLO").EndsWith(StringView(TEXT("xhello HELLO")), StringSearchCase::IgnoreCase) == false);
}
}
}
TEST_CASE("String Compare works") {
SECTION("String") {
SECTION("case sensitive") {
// Empty strings
CHECK(String("").Compare(String(TEXT("")), StringSearchCase::CaseSensitive) == 0);
CHECK(String("").Compare(String(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
CHECK(String("xxx").Compare(String(TEXT("")), StringSearchCase::CaseSensitive) > 0);
// Equal lengths, difference at end
CHECK(String("xxx").Compare(String(TEXT("xxx")), StringSearchCase::CaseSensitive) == 0);
CHECK(String("abc").Compare(String(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
CHECK(String("abd").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
// Equal lengths, difference in the middle
CHECK(String("abcx").Compare(String(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
CHECK(String("abdx").Compare(String(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
// Different lengths, same prefix
CHECK(String("abcxx").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
CHECK(String("abc").Compare(String(TEXT("abcxx")), StringSearchCase::CaseSensitive) < 0);
// Different lengths, different prefix
CHECK(String("abcx").Compare(String(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
CHECK(String("abd").Compare(String(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
CHECK(String("abc").Compare(String(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
CHECK(String("abdx").Compare(String(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
// Case differences
CHECK(String("a").Compare(String(TEXT("A")), StringSearchCase::CaseSensitive) > 0);
CHECK(String("A").Compare(String(TEXT("a")), StringSearchCase::CaseSensitive) < 0);
}
SECTION("ignore case") {
// Empty strings
CHECK(String("").Compare(String(TEXT("")), StringSearchCase::IgnoreCase) == 0);
CHECK(String("").Compare(String(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
CHECK(String("xxx").Compare(String(TEXT("")), StringSearchCase::IgnoreCase) > 0);
// Equal lengths, difference at end
CHECK(String("xxx").Compare(String(TEXT("xxx")), StringSearchCase::IgnoreCase) == 0);
CHECK(String("abc").Compare(String(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
CHECK(String("abd").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
// Equal lengths, difference in the middle
CHECK(String("abcx").Compare(String(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
CHECK(String("abdx").Compare(String(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
// Different lengths, same prefix
CHECK(String("abcxx").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
CHECK(String("abc").Compare(String(TEXT("abcxx")), StringSearchCase::IgnoreCase) < 0);
// Different lengths, different prefix
CHECK(String("abcx").Compare(String(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
CHECK(String("abd").Compare(String(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
CHECK(String("abc").Compare(String(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
CHECK(String("abdx").Compare(String(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
// Case differences
CHECK(String("a").Compare(String(TEXT("A")), StringSearchCase::IgnoreCase) == 0);
CHECK(String("A").Compare(String(TEXT("a")), StringSearchCase::IgnoreCase) == 0);
}
}
SECTION("StringView") {
SECTION("case sensitive") {
// Null string views
CHECK(StringView().Compare(StringView(), StringSearchCase::CaseSensitive) == 0);
CHECK(StringView().Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("xxx")).Compare(StringView(), StringSearchCase::CaseSensitive) > 0);
// Empty strings
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("")), StringSearchCase::CaseSensitive) == 0);
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("")), StringSearchCase::CaseSensitive) > 0);
// Equal lengths, difference at end
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("xxx")), StringSearchCase::CaseSensitive) == 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
// Equal lengths, difference in the middle
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
// Different lengths, same prefix
CHECK(StringView(TEXT("abcxx")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abcxx")), StringSearchCase::CaseSensitive) < 0);
// Different lengths, different prefix
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abd")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abcx")), StringSearchCase::CaseSensitive) > 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abdx")), StringSearchCase::CaseSensitive) < 0);
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abc")), StringSearchCase::CaseSensitive) > 0);
// Case differences
CHECK(StringView(TEXT("a")).Compare(StringView(TEXT("A")), StringSearchCase::CaseSensitive) > 0);
CHECK(StringView(TEXT("A")).Compare(StringView(TEXT("a")), StringSearchCase::CaseSensitive) < 0);
}
SECTION("ignore case") {
//Null string views
CHECK(StringView().Compare(StringView(), StringSearchCase::IgnoreCase) == 0);
CHECK(StringView().Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("xxx")).Compare(StringView(), StringSearchCase::IgnoreCase) > 0);
// Empty strings
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("")), StringSearchCase::IgnoreCase) == 0);
CHECK(StringView(TEXT("")).Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("")), StringSearchCase::IgnoreCase) > 0);
// Equal lengths, difference at end
CHECK(StringView(TEXT("xxx")).Compare(StringView(TEXT("xxx")), StringSearchCase::IgnoreCase) == 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
// Equal lengths, difference in the middle
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
// Different lengths, same prefix
CHECK(StringView(TEXT("abcxx")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abcxx")), StringSearchCase::IgnoreCase) < 0);
// Different lengths, different prefix
CHECK(StringView(TEXT("abcx")).Compare(StringView(TEXT("abd")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("abd")).Compare(StringView(TEXT("abcx")), StringSearchCase::IgnoreCase) > 0);
CHECK(StringView(TEXT("abc")).Compare(StringView(TEXT("abdx")), StringSearchCase::IgnoreCase) < 0);
CHECK(StringView(TEXT("abdx")).Compare(StringView(TEXT("abc")), StringSearchCase::IgnoreCase) > 0);
// Case differences
CHECK(StringView(TEXT("a")).Compare(StringView(TEXT("A")), StringSearchCase::IgnoreCase) == 0);
CHECK(StringView(TEXT("A")).Compare(StringView(TEXT("a")), StringSearchCase::IgnoreCase) == 0);
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// Application startup module.
/// </summary>
public class TestsMain : EngineModule
{
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#if PLATFORM_WINDOWS || PLATFORM_LINUX
#define CATCH_CONFIG_RUNNER
#include <ThirdParty/catch2/catch.hpp>
int main(int argc, char* argv[])
{
int result = Catch::Session().run(argc, argv);
return result;
}
#endif

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using System.Linq;
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// Target that builds standalone, native tests.
/// </summary>
public class FlaxNativeTestsTarget : EngineTarget
{
/// <inheritdoc />
public override void Init()
{
base.Init();
// Initialize
OutputName = "FlaxNativeTests";
ConfigurationName = "Tests";
// TODO: All platforms would benefit from the tests.
Platforms = new[]
{
TargetPlatform.Windows,
TargetPlatform.Linux,
};
Architectures = new[]
{
TargetArchitecture.x64,
TargetArchitecture.x86,
};
Modules.Remove("Main");
Modules.Add("TestsMain");
}
/// <inheritdoc />
public override void SetupTargetEnvironment(BuildOptions options)
{
base.SetupTargetEnvironment(options);
options.LinkEnv.LinkAsConsoleProgram = true;
// Setup output folder for Test binaries
var platformName = options.Platform.Target.ToString();
var architectureName = options.Architecture.ToString();
var configurationName = options.Configuration.ToString();
options.OutputFolder = Path.Combine(options.WorkingDirectory, "Binaries", "Tests", platformName, architectureName, configurationName);
}
/// <inheritdoc />
public override Target SelectReferencedTarget(ProjectInfo project, Target[] projectTargets)
{
var testTargetName = "FlaxNativeTests"; // Should this be added to .flaxproj, similarly as "GameTarget" and "EditorTarget"?
var result = projectTargets.FirstOrDefault(x => x.Name == testTargetName);
if (result == null)
// Apparently .NET compiler that is used for building .Build.cs files is different that one used for building Flax.Build itself. String interpolation ($) does't work here.
throw new Exception(string.Format("Invalid or missing test target {0} specified in project {1} (referenced by project {2}).", testTargetName, project.Name, Project.Name));
return result;
}
}

23
Source/ThirdParty/catch2/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

17959
Source/ThirdParty/catch2/catch.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build;
/// <summary>
/// https://github.com/catchorg/Catch2
/// </summary>
public class catch2 : HeaderOnlyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.BoostSoftwareLicense;
LicenseFilePath = "LICENSE.txt";
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
@@ -76,6 +76,11 @@ namespace Flax.Build.NativeCpp
/// </summary>
public bool GenerateWindowsMetadata = false;
/// <summary>
/// Use CONSOLE subsystem on Windows instead of the WINDOWS one.
/// </summary>
public bool LinkAsConsoleProgram = false;
/// <summary>
/// Enables documentation generation.
/// </summary>
@@ -114,6 +119,7 @@ namespace Flax.Build.NativeCpp
LinkTimeCodeGeneration = LinkTimeCodeGeneration,
UseIncrementalLinking = UseIncrementalLinking,
GenerateWindowsMetadata = GenerateWindowsMetadata,
LinkAsConsoleProgram = LinkAsConsoleProgram,
GenerateDocumentation = GenerateDocumentation
};
foreach (var e in InputFiles)

View File

@@ -675,7 +675,14 @@ namespace Flax.Build.Platforms
}
// Specify subsystem
args.Add("/SUBSYSTEM:WINDOWS");
if (linkEnvironment.LinkAsConsoleProgram)
{
args.Add("/SUBSYSTEM:CONSOLE");
}
else
{
args.Add("/SUBSYSTEM:WINDOWS");
}
// Generate Windows Metadata
if (linkEnvironment.GenerateWindowsMetadata)