1
0
Fork 0

micro optimizations for case conversion (#10291)

This commit is contained in:
Jan 2019-10-22 09:39:16 +02:00 committed by GitHub
parent 19fad3f0df
commit 09134ee8d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 176 additions and 126 deletions

View File

@ -351,7 +351,7 @@ DateSelectionModifier parseDateModifierFlag(VPackSlice flag) {
}
TRI_ASSERT(flagStr.size() >= 1);
std::transform(flagStr.begin(), flagStr.end(), flagStr.begin(), ::tolower);
basics::StringUtils::tolowerInPlace(flagStr);
switch (flagStr.front()) {
case 'y':
if (flagStr == "years" || flagStr == "year" || flagStr == "y") {
@ -3401,7 +3401,7 @@ AqlValue Functions::DateTrunc(ExpressionContext* expressionContext,
}
std::string duration = durationType.slice().copyString();
std::transform(duration.begin(), duration.end(), duration.begin(), ::tolower);
basics::StringUtils::tolowerInPlace(duration);
year_month_day ymd{floor<days>(tp)};
auto day_time = make_time(tp - sys_days(ymd));

View File

@ -1928,8 +1928,7 @@ int fetchEdgesFromEngines(
// Response has invalid format
return TRI_ERROR_HTTP_CORRUPTED_JSON;
}
read += Helper::getNumericValue<size_t>(resSlice,
"readIndex", 0);
read += Helper::getNumericValue<size_t>(resSlice, "readIndex", 0);
bool allCached = true;
VPackSlice edges = resSlice.get("edges");
@ -1974,14 +1973,14 @@ void fetchVerticesFromEngines(
std::unordered_map<arangodb::velocypack::StringRef, VPackSlice>& result,
std::vector<std::shared_ptr<VPackBufferUInt8>>& datalake,
bool forShortestPath) {
// TODO map id => ServerID if possible
// And go fast-path
// slow path, sharding not deducable from _id
transaction::BuilderLeaser leased(&trx);
leased->openObject();
leased->add(VPackValue("keys"));
leased->openArray();
leased->add("keys", VPackValue(VPackValueType::Array));
for (auto const& v : vertexIds) {
leased->add(VPackValuePair(v.data(), v.length(), VPackValueType::String));
}
@ -2025,7 +2024,7 @@ void fetchVerticesFromEngines(
}
bool cached = false;
for (auto pair : VPackObjectIterator(resSlice)) {
for (auto pair : VPackObjectIterator(resSlice, true)) {
arangodb::velocypack::StringRef key(pair.key);
if (vertexIds.erase(key) == 0) {
// We either found the same vertex twice,

View File

@ -120,7 +120,6 @@ template <SocketType T>
int HttpCommTask<T>::on_header_field(llhttp_t* p, const char* at, size_t len) {
HttpCommTask<T>* self = static_cast<HttpCommTask<T>*>(p->data);
if (self->_lastHeaderWasValue) {
StringUtils::tolowerInPlace(&self->_lastHeaderField);
self->_request->setHeaderV2(std::move(self->_lastHeaderField),
std::move(self->_lastHeaderValue));
self->_lastHeaderField.assign(at, len);
@ -147,7 +146,6 @@ template <SocketType T>
int HttpCommTask<T>::on_header_complete(llhttp_t* p) {
HttpCommTask<T>* self = static_cast<HttpCommTask<T>*>(p->data);
if (!self->_lastHeaderField.empty()) {
StringUtils::tolowerInPlace(&self->_lastHeaderField);
self->_request->setHeaderV2(std::move(self->_lastHeaderField),
std::move(self->_lastHeaderValue));
}
@ -695,11 +693,11 @@ void HttpCommTask<T>::sendResponse(std::unique_ptr<GeneralResponse> baseRes,
while (p < end) {
if (capState == 1) {
// upper case
header->push_back(::toupper(*p));
header->push_back(StringUtils::toupper(*p));
capState = 0;
} else if (capState == 0) {
// normal case
header->push_back(::tolower(*p));
header->push_back(StringUtils::tolower(*p));
if (*p == '-') {
capState = 1;
} else if (*p == ':') {

View File

@ -1739,7 +1739,7 @@ arangodb::Result fromFuncExists(irs::boolean_filter* filter, QueryContext const&
}
std::string strArg(arg);
arangodb::basics::StringUtils::tolowerInPlace(&strArg); // normalize user input
arangodb::basics::StringUtils::tolowerInPlace(strArg); // normalize user input
irs::string_ref const TypeAnalyzer("analyzer");
typedef bool (*TypeHandler)(std::string&, arangodb::iresearch::IResearchLinkMeta::Analyzer const&);

View File

@ -48,7 +48,7 @@ bool parseDirectionBool(arangodb::velocypack::Slice slice, bool& direction) {
bool parseDirectionString(arangodb::velocypack::Slice slice, bool& direction) {
if (slice.isString()) {
std::string value = arangodb::iresearch::getStringRef(slice);
arangodb::basics::StringUtils::tolowerInPlace(&value);
arangodb::basics::StringUtils::tolowerInPlace(value);
if (value == "asc") {
direction = true;

View File

@ -353,7 +353,7 @@ bool RestBatchHandler::getBoundaryHeader(std::string& result) {
// trim 2nd part and lowercase it
StringUtils::trimInPlace(parts[1]);
std::string p = parts[1].substr(0, boundaryLength);
StringUtils::tolowerInPlace(&p);
StringUtils::tolowerInPlace(p);
if (p != "boundary=") {
return false;
@ -501,7 +501,7 @@ bool RestBatchHandler::extractPart(SearchHelper& helper) {
if (key[0] == 'c' || key[0] == 'C') {
// got an interesting key. now process it
StringUtils::tolowerInPlace(&key);
StringUtils::tolowerInPlace(key);
// skip the colon itself
++colon;

View File

@ -72,24 +72,19 @@ RestStatus RestEdgesHandler::execute() {
}
bool RestEdgesHandler::parseDirection(TRI_edge_direction_e& direction) {
bool found;
std::string dir = _request->value("direction", found);
std::string const& dir = _request->value("direction");
if (!found || dir.empty()) {
dir = "any";
}
std::string dirString(dir);
if (dirString == "any") {
if (dir.empty()) {
direction = TRI_EDGE_ANY;
} else if (dirString == "out" || dirString == "outbound") {
} else if (dir == "any") {
direction = TRI_EDGE_ANY;
} else if (dir == "out" || dir == "outbound") {
direction = TRI_EDGE_OUT;
} else if (dirString == "in" || dirString == "inbound") {
} else if (dir == "in" || dir == "inbound") {
direction = TRI_EDGE_IN;
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER,
"<direction> must by any, in, or out, not: " + dirString);
"<direction> must by any, in, or out, not: " + dir);
return false;
}
@ -154,7 +149,7 @@ bool RestEdgesHandler::readEdges() {
return false;
}
std::string collectionName = suffixes[0];
std::string const& collectionName = suffixes[0];
if (!validateCollection(collectionName)) {
return false;
}
@ -197,7 +192,7 @@ bool RestEdgesHandler::readEdges() {
resultBuilder.add(edges);
resultBuilder.add(StaticStrings::Error, VPackValue(false));
resultBuilder.add("code", VPackValue(200));
resultBuilder.add(StaticStrings::Code, VPackValue(200));
VPackSlice stats = extras.get("stats");
if (stats.isObject()) {
resultBuilder.add(VPackValue("stats"));
@ -241,7 +236,7 @@ bool RestEdgesHandler::readEdgesForMultipleVertices() {
return false;
}
std::string collectionName = suffixes[0];
std::string const& collectionName = suffixes[0];
if (!validateCollection(collectionName)) {
return false;
}
@ -277,10 +272,8 @@ bool RestEdgesHandler::readEdgesForMultipleVertices() {
VPackSlice edges = queryResult.data->slice();
for (VPackSlice edge : VPackArrayIterator(edges)) {
std::string key = transaction::helpers::extractKeyFromDocument(edge).copyString();
if (foundEdges.find(key) == foundEdges.end()) {
if (foundEdges.emplace(transaction::helpers::extractKeyFromDocument(edge).copyString()).second) {
resultBuilder.add(edge);
foundEdges.emplace(std::move(key));
}
}
ctx = queryResult.context;
@ -288,7 +281,7 @@ bool RestEdgesHandler::readEdgesForMultipleVertices() {
resultBuilder.close(); // </edges>
resultBuilder.add(StaticStrings::Error, VPackValue(false));
resultBuilder.add("code", VPackValue(200));
resultBuilder.add(StaticStrings::Code, VPackValue(200));
resultBuilder.close();
// and generate a response

View File

@ -500,6 +500,11 @@ void SupervisedScheduler::sortoutLongRunningThreads() {
}
bool SupervisedScheduler::canPullFromQueue(uint64_t queueIndex) const {
if (queueIndex == 0) {
// We can always! pull from high priority
return true;
}
// This function should ensure the following thread reservation:
// 25% reserved for FastLane only
// upto 75% of work can go on MedLane and FastLane
@ -514,17 +519,13 @@ bool SupervisedScheduler::canPullFromQueue(uint64_t queueIndex) const {
uint64_t jobsDequeued = _jobsDequeued.load(std::memory_order_relaxed);
TRI_ASSERT(jobsDequeued >= jobsDone);
switch (queueIndex) {
case 0:
// We can always! pull from high priority
return true;
case 1:
// We can work on med if less than 75% of the workers are busy
return (jobsDequeued - jobsDone) < (_maxNumWorker * 3 / 4);
default:
// We can work on low if less than 50% of the workers are busy
return (jobsDequeued - jobsDone) < (_maxNumWorker / 2);
if (queueIndex == 1) {
// We can work on med if less than 75% of the workers are busy
return (jobsDequeued - jobsDone) < (_maxNumWorker * 3 / 4);
}
// We can work on low if less than 50% of the workers are busy
return (jobsDequeued - jobsDone) < (_maxNumWorker / 2);
}
std::unique_ptr<SupervisedScheduler::WorkItem> SupervisedScheduler::getWork(

View File

@ -140,7 +140,7 @@ Result arangodb::unregisterUserFunctionsGroup(TRI_vocbase_t& vocbase,
}
std::string uc(functionFilterPrefix);
basics::StringUtils::toupperInPlace(&uc);
basics::StringUtils::toupperInPlace(uc);
if ((uc.length() < 2) || (uc[uc.length() - 1] != ':') || (uc[uc.length() - 2] != ':')) {
uc += "::";
@ -279,7 +279,7 @@ Result arangodb::registerUserFunction(TRI_vocbase_t& vocbase, velocypack::Slice
return res;
}
std::string _key(name);
basics::StringUtils::toupperInPlace(&_key);
basics::StringUtils::toupperInPlace(_key);
VPackBuilder oneFunctionDocument;
oneFunctionDocument.openObject();
@ -336,7 +336,7 @@ Result arangodb::toArrayUserFunctions(TRI_vocbase_t& vocbase,
"@ucName RETURN function";
std::string uc(functionFilterPrefix);
basics::StringUtils::toupperInPlace(&uc);
basics::StringUtils::toupperInPlace(uc);
if ((uc.length() < 2) || (uc[uc.length() - 1] != ':') || (uc[uc.length() - 2] != ':')) {
uc += "::";

View File

@ -645,71 +645,97 @@ std::string replace(std::string const& sourceStr, std::string const& fromStr,
return std::string(ptr, k);
}
void tolowerInPlace(std::string* str) {
void tolowerInPlace(std::string& str) {
// unrolled version of
// for (auto& c : str) {
// c = StringUtils::tolower(c);
// }
auto pos = str.data();
auto end = pos + str.size();
if (str->empty()) {
return;
}
while (pos != end) {
size_t len = end - pos;
if (len > 4) {
len = 4;
}
for (std::string::iterator i = str->begin(); i != str->end(); ++i) {
*i = ::tolower(*i);
switch (len) {
case 4:
pos[3] = StringUtils::tolower(pos[3]);
[[fallthrough]];
case 3:
pos[2] = StringUtils::tolower(pos[2]);
[[fallthrough]];
case 2:
pos[1] = StringUtils::tolower(pos[1]);
[[fallthrough]];
case 1:
pos[0] = StringUtils::tolower(pos[0]);
}
pos += len;
}
}
std::string tolower(std::string&& str) {
std::transform(
str.begin(), str.end(), str.begin(), [](unsigned char c){ return ::tolower(c); });
tolowerInPlace(str);
return std::move(str);
}
std::string tolower(std::string const& str) {
size_t len = str.length();
if (len == 0) {
return "";
}
std::string result;
result.reserve(len);
result.resize(str.size());
char const* ptr = str.c_str();
for (; 0 < len; len--, ptr++) {
result.push_back(static_cast<char>(::tolower(*ptr)));
size_t i = 0;
for (auto& c : result) {
c = StringUtils::tolower(str[i++]);
}
return result;
}
void toupperInPlace(std::string* str) {
size_t len = str->length();
void toupperInPlace(std::string& str) {
// unrolled version of
// for (auto& c : str) {
// c = StringUtils::toupper(c);
// }
auto pos = str.data();
auto end = pos + str.size();
if (len == 0) {
return;
}
while (pos != end) {
size_t len = end - pos;
if (len > 4) {
len = 4;
}
for (std::string::iterator i = str->begin(); i != str->end(); ++i) {
*i = ::toupper(*i);
switch (len) {
case 4:
pos[3] = StringUtils::toupper(pos[3]);
[[fallthrough]];
case 3:
pos[2] = StringUtils::toupper(pos[2]);
[[fallthrough]];
case 2:
pos[1] = StringUtils::toupper(pos[1]);
[[fallthrough]];
case 1:
pos[0] = StringUtils::toupper(pos[0]);
}
pos += len;
}
}
std::string toupper(std::string&& str) {
toupperInPlace(str);
return std::move(str);
}
std::string toupper(std::string const& str) {
size_t len = str.length();
if (len == 0) {
return "";
}
std::string result;
result.reserve(len);
result.resize(str.size());
char const* ptr = str.c_str();
for (; 0 < len; len--, ptr++) {
result.push_back(static_cast<char>(::toupper(*ptr)));
size_t i = 0;
for (auto& c : result) {
c = StringUtils::toupper(str[i++]);
}
return result;
@ -842,14 +868,6 @@ std::string urlEncode(std::string const& str) {
return urlEncode(str.c_str(), str.size());
}
std::string urlEncode(char const* src) {
if (src != nullptr) {
size_t len = strlen(src);
return urlEncode(src, len);
}
return "";
}
std::string urlEncode(char const* src, size_t const len) {
static char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
@ -1401,7 +1419,7 @@ bool boolean(std::string const& str) {
return false;
}
std::string lower = trim(str);
tolowerInPlace(&lower);
tolowerInPlace(lower);
if (lower == "true" || lower == "yes" || lower == "on" || lower == "y" ||
lower == "1" || lower == "") {

View File

@ -159,17 +159,33 @@ std::vector<std::string> wrap(std::string const& sourceStr, size_t size,
std::string replace(std::string const& sourceStr, std::string const& fromString,
std::string const& toString);
/// @brief converts string to lower case in place
void tolowerInPlace(std::string* str);
static inline char tolower(char c) {
return c + ((static_cast<unsigned char>(c - 65) < 26U) << 5);
}
/// @brief converts string to lower case
static inline unsigned char tolower(unsigned char c) {
return c + ((c - 65U < 26U) << 5);
}
static inline char toupper(char c) {
return c - ((static_cast<unsigned char>(c - 97) < 26U) << 5);
}
static inline unsigned char toupper(unsigned char c) {
return c - ((c - 97U < 26U) << 5);
}
/// @brief converts string to lower case in place - locale-independent, ASCII only!
void tolowerInPlace(std::string& str);
/// @brief converts string to lower case - locale-independent, ASCII only!
std::string tolower(std::string&& str);
std::string tolower(std::string const& str);
/// @brief converts string to upper case in place
void toupperInPlace(std::string* str);
/// @brief converts string to upper case in place - locale-independent, ASCII only!
void toupperInPlace(std::string& str);
/// @brief converts string to upper case
/// @brief converts string to upper case - locale-independent, ASCII only!
std::string toupper(std::string const& str);
/// @brief checks for a prefix
@ -182,9 +198,6 @@ bool isSuffix(std::string const& str, std::string const& postfix);
std::string urlDecodePath(std::string const& str);
std::string urlDecode(std::string const& str);
/// @brief url encodes the string
std::string urlEncode(char const* src);
/// @brief url encodes the string
std::string urlEncode(char const* src, size_t len);

View File

@ -166,7 +166,8 @@ class SharedState {
[[fallthrough]];
case State::OnlyResult:
if (_state.compare_exchange_strong(state, State::Done, std::memory_order_release)) {
// acquire is actually correct here
if (_state.compare_exchange_strong(state, State::Done, std::memory_order_acquire)) {
doCallback();
return;
}
@ -202,7 +203,8 @@ class SharedState {
[[fallthrough]];
case State::OnlyCallback:
if (_state.compare_exchange_strong(state, State::Done, std::memory_order_release)) {
// acquire is actually correct here
if (_state.compare_exchange_strong(state, State::Done, std::memory_order_acquire)) {
doCallback();
return;
}

View File

@ -35,6 +35,7 @@
#include <s2/s2polygon.h>
#include <s2/s2polyline.h>
#include "Basics/StringUtils.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/debugging.h"
#include "Geo/GeoParams.h"
@ -56,7 +57,7 @@ const std::string kTypeStringGeometryCollection = "GeometryCollection";
namespace {
inline bool sameCharIgnoringCase(char a, char b) {
return std::toupper(a) == std::toupper(b);
return arangodb::basics::StringUtils::toupper(a) == arangodb::basics::StringUtils::toupper(b);
}
} // namespace

View File

@ -99,7 +99,7 @@ void HttpRequest::parseHeader(char* start, size_t length) {
char* e = lineBegin;
for (; e < end && *e != ' ' && *e != '\n'; ++e) {
*e = ::tolower(*e);
*e = ::StringUtils::tolower(*e);
}
// store key and value
@ -325,7 +325,7 @@ void HttpRequest::parseHeader(char* start, size_t length) {
char* e = lineBegin;
for (; e < end && *e != ':' && *e != '\n'; ++e) {
*e = ::tolower(*e);
*e = StringUtils::tolower(*e);
}
// store key and value
@ -535,7 +535,7 @@ void HttpRequest::parseUrl(const char* path, size_t length) {
}
void HttpRequest::setHeaderV2(std::string key, std::string value) {
StringUtils::tolowerInPlace(&key); // always lowercase key
StringUtils::tolowerInPlace(key); // always lowercase key
if (key == StaticStrings::ContentLength) {
_contentLength = NumberUtils::atoi_zero<int64_t>(value.c_str(), value.c_str() + value.size());
@ -570,7 +570,7 @@ void HttpRequest::setHeaderV2(std::string key, std::string value) {
if (key == "x-http-method" ||
key == "x-method-override" ||
key == "x-http-method-override") {
StringUtils::tolowerInPlace(&value);
StringUtils::tolowerInPlace(value);
_type = findRequestType(value.c_str(), value.size());
// don't insert this header!!
return;
@ -742,7 +742,7 @@ void HttpRequest::setHeader(char const* key, size_t keyLength,
(keyLength == 17 && memcmp(key, "x-method-override", keyLength) == 0) ||
(keyLength == 22 && memcmp(key, "x-http-method-override", keyLength) == 0)) {
std::string overriddenType(value, valueLength);
StringUtils::tolowerInPlace(&overriddenType);
StringUtils::tolowerInPlace(overriddenType);
_type = findRequestType(overriddenType.c_str(), overriddenType.size());

View File

@ -189,11 +189,11 @@ void HttpResponse::writeHeader(StringBuffer* output) {
while (p < end) {
if (capState == 1) {
// upper case
output->appendCharUnsafe(::toupper(*p));
output->appendCharUnsafe(StringUtils::toupper(*p));
capState = 0;
} else if (capState == 0) {
// normal case
output->appendCharUnsafe(::tolower(*p));
output->appendCharUnsafe(StringUtils::tolower(*p));
if (*p == '-') {
capState = 1;
} else if (*p == ':') {

View File

@ -129,11 +129,11 @@ void VstResponse::writeMessageHeader(VPackBuffer<uint8_t>& buffer) const {
for (auto& it : tmp) {
if (capState == 1) {
// upper case
it = ::toupper(it);
it = StringUtils::toupper(it);
capState = 0;
} else if (capState == 0) {
// normal case
it = ::tolower(it);
it = StringUtils::tolower(it);
if (it == '-') {
capState = 1;
} else if (it == ':') {

View File

@ -133,7 +133,7 @@ void SimpleHttpResult::addHeaderField(char const* key, size_t keyLength,
// lower-case key
std::string keyString(key, keyLength);
StringUtils::tolowerInPlace(&keyString);
StringUtils::tolowerInPlace(keyString);
// trim value
{

View File

@ -2113,8 +2113,8 @@ static void JS_Log(v8::FunctionCallbackInfo<v8::Value> const& args) {
std::string prefix;
StringUtils::tolowerInPlace(&ls);
StringUtils::tolowerInPlace(&ts);
StringUtils::tolowerInPlace(ls);
StringUtils::tolowerInPlace(ts);
LogTopic const* topicPtr = ts.empty() ? nullptr : LogTopic::lookup(ts);
LogTopic const& topic = (topicPtr != nullptr) ? *topicPtr : Logger::FIXME;
@ -4007,7 +4007,7 @@ static void JS_PBKDF2(v8::FunctionCallbackInfo<v8::Value> const& args) {
SslInterface::Algorithm al = SslInterface::Algorithm::ALGORITHM_SHA1;
if (args.Length() > 4 && !args[4]->IsUndefined()) {
std::string algorithm = TRI_ObjectToString(isolate, args[4]);
StringUtils::tolowerInPlace(&algorithm);
StringUtils::tolowerInPlace(algorithm);
if (algorithm == "sha1") {
al = SslInterface::Algorithm::ALGORITHM_SHA1;
@ -4061,7 +4061,7 @@ static void JS_HMAC(v8::FunctionCallbackInfo<v8::Value> const& args) {
SslInterface::Algorithm al = SslInterface::Algorithm::ALGORITHM_SHA256;
if (args.Length() > 2 && !args[2]->IsUndefined()) {
std::string algorithm = TRI_ObjectToString(isolate, args[2]);
StringUtils::tolowerInPlace(&algorithm);
StringUtils::tolowerInPlace(algorithm);
if (algorithm == "sha1") {
al = SslInterface::Algorithm::ALGORITHM_SHA1;

View File

@ -131,13 +131,38 @@ TEST_F(StringUtilsTest, test_Split3) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test_Tolower
/// @brief test_tolower
////////////////////////////////////////////////////////////////////////////////
TEST_F(StringUtilsTest, test_Tolower) {
string lower = StringUtils::tolower("HaLlO WoRlD!");
TEST_F(StringUtilsTest, test_tolower) {
EXPECT_EQ(StringUtils::tolower(""), "");
EXPECT_EQ(StringUtils::tolower(" "), " ");
EXPECT_EQ(StringUtils::tolower("12345"), "12345");
EXPECT_EQ(StringUtils::tolower("a"), "a");
EXPECT_EQ(StringUtils::tolower("A"), "a");
EXPECT_EQ(StringUtils::tolower("ä"), "ä");
EXPECT_EQ(StringUtils::tolower("Ä"), "Ä");
EXPECT_EQ(StringUtils::tolower("HeLlO WoRlD!"), "hello world!");
EXPECT_EQ(StringUtils::tolower("hello-world-nono "), "hello-world-nono ");
EXPECT_EQ(StringUtils::tolower("HELLo-world-NONO "), "hello-world-nono ");
EXPECT_EQ(StringUtils::tolower(" The quick \r\nbrown Fox"), " the quick \r\nbrown fox");
}
EXPECT_EQ(lower, "hallo world!");
////////////////////////////////////////////////////////////////////////////////
/// @brief test_toupper
////////////////////////////////////////////////////////////////////////////////
TEST_F(StringUtilsTest, test_toupper) {
EXPECT_EQ(StringUtils::toupper(""), "");
EXPECT_EQ(StringUtils::toupper(" "), " ");
EXPECT_EQ(StringUtils::toupper("12345"), "12345");
EXPECT_EQ(StringUtils::toupper("a"), "A");
EXPECT_EQ(StringUtils::toupper("A"), "A");
EXPECT_EQ(StringUtils::toupper("ä"), "ä");
EXPECT_EQ(StringUtils::toupper("Ä"), "Ä");
EXPECT_EQ(StringUtils::toupper("HeLlO WoRlD!"), "HELLO WORLD!");
EXPECT_EQ(StringUtils::toupper("hello-world-nono "), "HELLO-WORLD-NONO ");
EXPECT_EQ(StringUtils::toupper("HELLo-world-NONO "), "HELLO-WORLD-NONO ");
}
////////////////////////////////////////////////////////////////////////////////