Skip to content

Commit

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

Overloaded `String::hexadecimal()` so users can pass `GuaranteeMap` as
arguments.
Added std::set<char> for hexadecimal as per need.

* tests: Added tests for string guarantees in `String::hexadecimal()`
  • Loading branch information
braw-lee authored Nov 24, 2023
1 parent b28534c commit d11f90f
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
20 changes: 20 additions & 0 deletions include/faker-cxx/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,26 @@ class String
static std::string hexadecimal(unsigned length = 1, HexCasing casing = HexCasing::Lower,
HexPrefix prefix = HexPrefix::ZeroX);

/**
* @brief Generates a hexadecimal string.
*
* @param guarantee A map specifying char count constraints if any
* @param length The number of digits to generate. Defaults to `1`.
* @param casing Casing of the generated string. Defaults to `HexCasing::Lower`.
* @param prefix Prefix for the generated string. Defaults to `0x`.
*
* @returns Hexadecimal string.
*
* @code
* String::hexadecimal({}) // "0xb"
* String::hexadecimal({'a',{2,2}}, 10) // "0xae13d04acb"
* String::hexadecimal({'F', {2,4}}, 6, HexCasing::Upper, HexPrefix::Hash) // "#E3FFF0"
* String::hexadecimal({'1', {1,4}, {'2', {1, 4}, {'c', {1,1}}, 6, HexCasing::Lower, HexPrefix::None) // "121a1c"
* @endcode
*/
static std::string hexadecimal(GuaranteeMap&& guarantee, unsigned length = 1, HexCasing casing = HexCasing::Lower,
HexPrefix prefix = HexPrefix::ZeroX);

/**
* @brief Generates a binary string.
*
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 @@ -35,6 +35,11 @@ const std::map<HexPrefix, std::string> hexPrefixToStringMapping{
{HexPrefix::Hash, "#"},
{HexPrefix::None, ""},
};

const std::map<HexCasing, std::set<char>> hexCasingToCharSetMapping{
{HexCasing::Lower, hexLowerCharSet},
{HexCasing::Upper, hexUpperCharSet},
};
}

bool isValidGuarantee(GuaranteeMap& guarantee, std::set<char>& targetCharacters, unsigned int length)
Expand Down Expand Up @@ -206,6 +211,18 @@ std::string String::hexadecimal(unsigned int length, HexCasing casing, HexPrefix
return hexadecimal;
}

std::string String::hexadecimal(GuaranteeMap&& guarantee, unsigned int length, HexCasing casing, HexPrefix prefix)
{
std::set<char> targetCharacters = hexCasingToCharSetMapping.at(casing);
// throw if guarantee is invalid
if (!isValidGuarantee(guarantee, targetCharacters, length))
{
throw std::invalid_argument{"Invalid guarantee."};
}
const auto& hexadecimalPrefix = hexPrefixToStringMapping.at(prefix);
return hexadecimalPrefix + generateStringWithGuarantee(guarantee, targetCharacters, length);
}

std::string String::binary(unsigned int length)
{
std::string binaryNumber;
Expand Down
141 changes: 141 additions & 0 deletions src/modules/string/StringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,147 @@ TEST_F(StringTest, shouldGenerateHexadecimalWithoutPrefix)
{ return hexUpperCharacters.find(hexNumberCharacter) != std::string::npos; }));
}

TEST_F(StringTest, shouldGenerateHexadecimalWithGuarantee1)
{
const auto hexadecimalLength = 20;
// exactly 4 'a'
// atleast 3 'f'
// atmost 10 'f'
faker::GuaranteeMap guarantee{{'a', {4, 4}}, {'f', {3, 10}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < 20; ++i)
{
auto copyGuarantee = guarantee;
const auto hexadecimal = String::hexadecimal(std::move(copyGuarantee), hexadecimalLength);
const auto prefix = hexadecimal.substr(0, 2);
const auto hexNumber = hexadecimal.substr(2);

ASSERT_EQ(hexNumber.size(), hexadecimalLength);
ASSERT_EQ(prefix, "0x");
ASSERT_TRUE(std::ranges::any_of(hexNumber, [hexNumber](char hexNumberCharacter)
{ return hexLowerCharacters.find(hexNumberCharacter) != std::string::npos; }));

auto count_a = std::ranges::count(hexNumber, 'a');
auto count_f = std::ranges::count(hexNumber, 'f');
ASSERT_TRUE(count_a == 4);
ASSERT_TRUE(count_f >= 3 && count_f <= 10);
}
}

TEST_F(StringTest, shouldGenerateHexadecimalWithGuarantee2)
{
const auto hexadecimalLength = 20;
// exactly 10 'F' - 0 'A'
// atleast 5 '0'
faker::GuaranteeMap guarantee{{'A', {0, 0}}, {'F', {10, 10}}, {'0', {5}}};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < 20; ++i)
{
auto copyGuarantee = guarantee;
const auto hexadecimal = String::hexadecimal(std::move(copyGuarantee), hexadecimalLength, HexCasing::Upper);
const auto prefix = hexadecimal.substr(0, 2);
const auto hexNumber = hexadecimal.substr(2);

ASSERT_EQ(hexNumber.size(), hexadecimalLength);
ASSERT_EQ(prefix, "0x");
ASSERT_TRUE(std::ranges::any_of(hexNumber, [hexNumber](char hexNumberCharacter)
{ return hexUpperCharacters.find(hexNumberCharacter) != std::string::npos; }));

auto count_A = std::ranges::count(hexNumber, 'A');
auto count_F = std::ranges::count(hexNumber, 'F');
auto count_0 = std::ranges::count(hexNumber, '0');
ASSERT_TRUE(count_A == 0);
ASSERT_TRUE(count_F == 10);
ASSERT_TRUE(count_0 >= 5);
}
}

TEST_F(StringTest, shouldGenerateHexadecimalWithGuarantee3)
{
const auto hexadecimalLength = 20;
// exactly 0 '0' '1' '2' '3' '4' '5' '6' '7'
faker::GuaranteeMap guarantee{
{'0', {0, 0}}, {'1', {0, 0}}, {'2', {0, 0}}, {'3', {0, 0}},
{'4', {0, 0}}, {'5', {0, 0}}, {'6', {0, 0}}, {'7', {0, 0}},
};
// it is a random function so lets test for 20 random generations
for (int i = 0; i < 20; ++i)
{
auto copyGuarantee = guarantee;
const auto hexadecimal = String::hexadecimal(std::move(copyGuarantee), hexadecimalLength, HexCasing::Upper);
const auto prefix = hexadecimal.substr(0, 2);
const auto hexNumber = hexadecimal.substr(2);

ASSERT_EQ(hexNumber.size(), hexadecimalLength);
ASSERT_EQ(prefix, "0x");
ASSERT_TRUE(std::ranges::any_of(hexNumber, [hexNumber](char hexNumberCharacter)
{ return hexUpperCharacters.find(hexNumberCharacter) != std::string::npos; }));

auto count_0 = std::ranges::count(hexNumber, '0');
auto count_1 = std::ranges::count(hexNumber, '1');
auto count_2 = std::ranges::count(hexNumber, '2');
auto count_3 = std::ranges::count(hexNumber, '3');
auto count_4 = std::ranges::count(hexNumber, '4');
auto count_5 = std::ranges::count(hexNumber, '5');
auto count_6 = std::ranges::count(hexNumber, '6');
auto count_7 = std::ranges::count(hexNumber, '7');
ASSERT_TRUE(count_0 == 0);
ASSERT_TRUE(count_1 == 0);
ASSERT_TRUE(count_2 == 0);
ASSERT_TRUE(count_3 == 0);
ASSERT_TRUE(count_4 == 0);
ASSERT_TRUE(count_5 == 0);
ASSERT_TRUE(count_6 == 0);
ASSERT_TRUE(count_7 == 0);
}
}

TEST_F(StringTest, invalidGuaranteeForHexadecimal1)
{
const auto hexadecimalLength = 20;
// atleast 5 'G' // invalid // 'G' is not a valid char for hexadecimal numbers
faker::GuaranteeMap guarantee{{'A', {0, 0}}, {'F', {10, 10}}, {'G', {5}}};
ASSERT_THROW(String::hexadecimal(std::move(guarantee), hexadecimalLength, HexCasing::Upper), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForHexadecimal2)
{
const auto hexadecimalLength = 20;
// atleast 5 'F' // invalid // 'F' is not a valid char for hexadecimal numbers with HexCasing::Lower
faker::GuaranteeMap guarantee{{'a', {0, 0}}, {'F', {10, 10}}, {'1', {5}}};
ASSERT_THROW(String::hexadecimal(std::move(guarantee), hexadecimalLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForHexadecimal3)
{
const auto hexadecimalLength = 20;
// atleast 5 'F' // invalid // 'F' is not a valid char for hexadecimal numbers with HexCasing::Lower
faker::GuaranteeMap guarantee{{'a', {0, 0}}, {'F', {10, 10}}, {'1', {5}}};
ASSERT_THROW(String::hexadecimal(std::move(guarantee), hexadecimalLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForHexadecimal4)
{
const auto hexadecimalLength = 10;
// atleast 5 'a' - 8 'b' // invalid // string size will be atleast 13 which is wrong
// atmost 10 'b' - 3 '1'
faker::GuaranteeMap guarantee{{'a', {5}}, {'b', {8, 10}}, {'1', {0, 3}}};
ASSERT_THROW(String::hexadecimal(std::move(guarantee), hexadecimalLength), std::invalid_argument);
}

TEST_F(StringTest, invalidGuaranteeForHexadecimal5)
{
const auto hexadecimalLength = 20;
// atmost 1 '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' // invalid // string size wont exceed 16
// which is wrong
faker::GuaranteeMap guarantee{
{'0', {0, 1}}, {'1', {0, 1}}, {'2', {0, 1}}, {'3', {0, 1}}, {'4', {0, 1}}, {'5', {0, 1}},
{'6', {0, 1}}, {'7', {0, 1}}, {'8', {0, 1}}, {'9', {0, 1}}, {'a', {0, 1}}, {'b', {0, 1}},
{'c', {0, 1}}, {'d', {0, 1}}, {'e', {0, 1}}, {'f', {0, 1}},
};
ASSERT_THROW(String::hexadecimal(std::move(guarantee), hexadecimalLength), std::invalid_argument);
}

TEST_F(StringTest, shouldGenerateBinary)
{
const auto binaryLength = 8;
Expand Down
4 changes: 4 additions & 0 deletions src/modules/string/data/Characters.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <set>
#include <string>

namespace faker
Expand All @@ -15,4 +16,7 @@ const std::string mixedAlphanumericCharacters = upperCharacters + lowerCharacter
const std::string hexUpperCharacters = "0123456789ABCDEF";
const std::string hexLowerCharacters = "0123456789abcdef";
const std::string symbolCharacters = "~`!@#$%^&*()_-+={[}]|:;\"'<,>.?/";

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'};
}

0 comments on commit d11f90f

Please sign in to comment.