Skip to content

Commit

Permalink
feature: Implemented string guarantee for String::alpha() (#343)
Browse files Browse the repository at this point in the history
* feature: Implemented string guarantee for `String::aplha()`

* tests: Added tests for string guarantee in `String::alpha()`
  • Loading branch information
braw-lee authored Nov 26, 2023
1 parent 2fb1853 commit b61a1d8
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
17 changes: 17 additions & 0 deletions include/faker-cxx/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ class String
*/
static std::string alpha(unsigned length = 1, StringCasing casing = StringCasing::Mixed);

/**
* @brief Generates a string consisting of letters in the English alphabet.
*
* @param guarantee A map specifying char count constraints if any
* @param length The number of characters to generate. Defaults to `1`.
* @param casing The casing of the characters. Defaults to `StringCasing::Mixed`.
*
* @returns Alpha string.
*
* @code
* String::alpha({}) // "b"
* String::alpha({{'A',{2,2}}, 5, StringCasing::Upper) // "DACAC"
* String::alpha({{'a',{0,0}},{'b',{3,3}},{'c', {0,2}}}, 10, StringCasing::Lower) // "bicnmmkbbp"
* @endcode
*/
static std::string alpha(GuaranteeMap&& guarantee, unsigned length = 1, StringCasing casing = StringCasing::Mixed);

/**
* @brief Generates a string consisting of alpha characters and digits.
*
Expand Down
17 changes: 17 additions & 0 deletions src/modules/string/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ const std::map<HexPrefix, std::string> hexPrefixToStringMapping{
{HexPrefix::None, ""},
};

const std::map<StringCasing, std::set<char>> stringCasingToAlphaCharSetMapping{
{StringCasing::Lower, lowerCharSet},
{StringCasing::Upper, upperCharSet},
{StringCasing::Mixed, mixedAlphaCharSet},
};

const std::map<HexCasing, std::set<char>> hexCasingToCharSetMapping{
{HexCasing::Lower, hexLowerCharSet},
{HexCasing::Upper, hexUpperCharSet},
Expand Down Expand Up @@ -152,6 +158,17 @@ std::string String::alpha(unsigned length, StringCasing casing)
return alpha;
}

std::string String::alpha(GuaranteeMap&& guarantee, unsigned int length, StringCasing casing)
{
auto targetCharacters = stringCasingToAlphaCharSetMapping.at(casing);
// throw if guarantee is invalid
if (!isValidGuarantee(guarantee, targetCharacters, length))
{
throw std::invalid_argument{"Invalid guarantee."};
}
return generateStringWithGuarantee(guarantee, targetCharacters, length);
}

std::string String::alphanumeric(unsigned int length, StringCasing casing, const std::string& excludeCharacters)
{
const auto& alphanumericCharacters = stringCasingToAlphanumericCharactersMapping.at(casing);
Expand Down
147 changes: 147 additions & 0 deletions src/modules/string/StringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,153 @@ TEST_F(StringTest, shouldGenerateLowerAlpha)
}));
}

TEST_F(StringTest, shouldGenerateMixedAlphaWithGuarantee)
{
const auto alphaLength = 20;
// exactly 5 'a'
// atleast 5 'A' - 3 'B' - 3 'z'
// atmost 20 'A' - 20 'B' - 6 'z'
const GuaranteeMap&& guarantee{{'A', {5, 20}}, {'B', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alpha = String::alpha(std::move(copyGuarantee), alphaLength);

ASSERT_EQ(alpha.size(), alphaLength);
ASSERT_TRUE(std::ranges::all_of(alpha,
[](char alphaCharacter)
{
return std::ranges::any_of(mixedAlphaCharacters,
[alphaCharacter](char mixedCharacter)
{ return mixedCharacter == alphaCharacter; });
}));
auto count_A = std::ranges::count(alpha, 'A');
auto count_B = std::ranges::count(alpha, 'B');
auto count_a = std::ranges::count(alpha, 'a');
auto count_z = std::ranges::count(alpha, 'z');

ASSERT_TRUE(count_A >= 5 && count_A <= 20);
ASSERT_TRUE(count_B >= 3 && count_B <= 20);
ASSERT_TRUE(count_a == 5);
ASSERT_TRUE(count_z >= 3 && count_z <= 6);
}
}

TEST_F(StringTest, shouldGenerateLowerAlphaWithGuarantee)
{
const auto alphaLength = 20;
// exactly 5 'a'
// atleast 5 'k' - 3 'o' - 3 'z'
// atmost 20 'k' - 20 'o' - 6 'z'
const GuaranteeMap&& guarantee{{'k', {5, 20}}, {'o', {3, 20}}, {'a', {5, 5}}, {'z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alpha = String::alpha(std::move(copyGuarantee), alphaLength, StringCasing::Lower);

ASSERT_EQ(alpha.size(), alphaLength);
ASSERT_TRUE(std::ranges::all_of(alpha,
[](char alphaCharacter)
{
return std::ranges::any_of(lowerCharSet,
[alphaCharacter](char lowerCharacter)
{ return lowerCharacter == alphaCharacter; });
}));
auto count_k = std::ranges::count(alpha, 'k');
auto count_o = std::ranges::count(alpha, 'o');
auto count_a = std::ranges::count(alpha, 'a');
auto count_z = std::ranges::count(alpha, 'z');

ASSERT_TRUE(count_k >= 5 && count_k <= 20);
ASSERT_TRUE(count_o >= 3 && count_o <= 20);
ASSERT_TRUE(count_a == 5);
ASSERT_TRUE(count_z >= 3 && count_z <= 6);
}
}

TEST_F(StringTest, shouldGenerateUpperAlphaWithGuarantee)
{
const auto alphaLength = 20;
// exactly 5 'A'
// atleast 5 'K' - 3 'O' - 3 'Z'
// atmost 20 'K' - 20 'O' - 6 'Z'
const GuaranteeMap&& guarantee{{'K', {5, 20}}, {'O', {3, 20}}, {'A', {5, 5}}, {'Z', {3, 6}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < runCount; ++i)
{
auto copyGuarantee = guarantee;
const auto alpha = String::alpha(std::move(copyGuarantee), alphaLength, StringCasing::Upper);

ASSERT_EQ(alpha.size(), alphaLength);
ASSERT_TRUE(std::ranges::all_of(alpha,
[](char alphaCharacter)
{
return std::ranges::any_of(upperCharSet,
[alphaCharacter](char lowerCharacter)
{ return lowerCharacter == alphaCharacter; });
}));
auto count_K = std::ranges::count(alpha, 'K');
auto count_O = std::ranges::count(alpha, 'O');
auto count_A = std::ranges::count(alpha, 'A');
auto count_Z = std::ranges::count(alpha, 'Z');

ASSERT_TRUE(count_K >= 5 && count_K <= 20);
ASSERT_TRUE(count_O >= 3 && count_O <= 20);
ASSERT_TRUE(count_A == 5);
ASSERT_TRUE(count_Z >= 3 && count_Z <= 6);
}
}

TEST_F(StringTest, invalidGuaranteeForAlpha1)
{
const auto alphaLength = 20;
// exactly 3 'Z'
// atleast 8 'A' - 10 'B' 1 'Y' // invalid // string size will be atleast 22 which is invalid
// atmost 10 'A','Y' - 15 'B'
GuaranteeMap guarantee = {{'A', {8, 10}}, {'B', {10, 15}}, {'Y', {1, 10}}, {'Z', {3, 3}}};
ASSERT_THROW(String::alpha(std::move(guarantee), alphaLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlpha2)
{
const auto alphaLength = 30;
// atmost 1 'A','B','C',D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
// invalid // string size won't exceed 26 which is invalid
GuaranteeMap guarantee = {
{'A', {0, 1}}, {'B', {0, 1}}, {'C', {0, 1}}, {'D', {0, 1}}, {'E', {0, 1}}, {'F', {0, 1}}, {'G', {0, 1}},
{'H', {0, 1}}, {'I', {0, 1}}, {'J', {0, 1}}, {'K', {0, 1}}, {'L', {0, 1}}, {'M', {0, 1}}, {'N', {0, 1}},
{'O', {0, 1}}, {'P', {0, 1}}, {'Q', {0, 1}}, {'R', {0, 1}}, {'S', {0, 1}}, {'T', {0, 1}}, {'U', {0, 1}},
{'V', {0, 1}}, {'W', {0, 1}}, {'X', {0, 1}}, {'Y', {0, 1}}, {'Z', {0, 1}},
};
ASSERT_THROW(String::alpha(std::move(guarantee), alphaLength, StringCasing::Upper), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlpha3)
{
const auto alphaLength = 20;
// atleast 4 '5' // invalid // alpha can't have digits
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}, {'5', {4, 6}}};
ASSERT_THROW(String::alpha(std::move(guarantee), alphaLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlpha4)
{
const auto alphaLength = 20;
// atleast 4 'a' // invalid // Can't have lower case characters when string casing is set to StringCasing::Upper
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}};
ASSERT_THROW(String::alpha(std::move(guarantee), alphaLength, StringCasing::Upper), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForAlpha5)
{
const auto alphaLength = 20;
// atleast 4 'B' // invalid // Can't have upper case characters when string casing is set to StringCasing::Lower
GuaranteeMap guarantee = {{'a', {4, 10}}, {'B', {4, 10}}};
ASSERT_THROW(String::alpha(std::move(guarantee), alphaLength, StringCasing::Lower), std::invalid_argument);
}

TEST_F(StringTest, shouldGenerateDefaultAphanumeric)
{
const auto alphanumeric = String::alphanumeric();
Expand Down
13 changes: 13 additions & 0 deletions src/modules/string/data/Characters.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ const std::string hexUpperCharacters = "0123456789ABCDEF";
const std::string hexLowerCharacters = "0123456789abcdef";
const std::string symbolCharacters = "~`!@#$%^&*()_-+={[}]|:;\"'<,>.?/";

const std::set<char> lowerCharSet{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
};
const std::set<char> upperCharSet{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
const std::set<char> mixedAlphaCharSet{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
const std::set<char> hexUpperCharSet{'A', 'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
const std::set<char> hexLowerCharSet{'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
const std::set<char> digitSet{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
Expand Down

0 comments on commit b61a1d8

Please sign in to comment.