From 2c9116cb667fa2494893e2bad7ca6b644b9a0f15 Mon Sep 17 00:00:00 2001 From: brawlee Date: Sat, 2 Dec 2023 09:26:24 +0530 Subject: [PATCH 1/2] feature: Implemented string guarantee for `String::sample()` --- include/faker-cxx/String.h | 15 +++++++++++++++ src/modules/string/String.cpp | 11 +++++++++++ src/modules/string/data/Characters.h | 7 +++++++ 3 files changed, 33 insertions(+) diff --git a/include/faker-cxx/String.h b/include/faker-cxx/String.h index 229a07ad0..99df81458 100644 --- a/include/faker-cxx/String.h +++ b/include/faker-cxx/String.h @@ -140,6 +140,21 @@ class String */ static std::string sample(unsigned length = 10); + /** + * @brief Returns a string containing UTF-16 chars between 33 and 125 (`!` to `}`). + * + * @param guarantee A map specifying char count constraints if any + * @param length The number of characters to generate. Defaults to `10`. + * + * @returns Sample string. + * + * @code + * String::sample({}) // "Zo!.:*e>wR" + * String::sample({{'|' ,{2,2}},{'^',{0,0}},{':',{1,8}}}, 8) // "|6Bye8:|" + * @endcode + */ + static std::string sample(GuaranteeMap&& guarantee, unsigned length = 10); + /** * @brief Generates a string consisting of given characters. * diff --git a/src/modules/string/String.cpp b/src/modules/string/String.cpp index 097888997..957423147 100644 --- a/src/modules/string/String.cpp +++ b/src/modules/string/String.cpp @@ -132,6 +132,17 @@ std::string String::sample(unsigned int length) return sample; } +std::string String::sample(GuaranteeMap&& guarantee, unsigned int length) +{ + auto targetCharacters = utf16CharSet; + // throw if guarantee is invalid + if (!isValidGuarantee(guarantee, targetCharacters, length)) + { + throw std::invalid_argument{"Invalid guarantee."}; + } + return generateStringWithGuarantee(guarantee, targetCharacters, length); +} + std::string String::fromCharacters(const std::string& characters, unsigned int length) { std::string result; diff --git a/src/modules/string/data/Characters.h b/src/modules/string/data/Characters.h index ca82334cb..b16fadfd1 100644 --- a/src/modules/string/data/Characters.h +++ b/src/modules/string/data/Characters.h @@ -33,4 +33,11 @@ const std::set mixedAlphaCharSet{ const std::set hexUpperCharSet{'A', 'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; const std::set hexLowerCharSet{'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; const std::set digitSet{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; +const std::set utf16CharSet{ + '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', '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', '{', '|', '}', +}; } From a49211002bf9982fd9ff56578c77b71c4cc38dfd Mon Sep 17 00:00:00 2001 From: brawlee Date: Sat, 2 Dec 2023 09:26:58 +0530 Subject: [PATCH 2/2] tests: Added tests for string guarantee in `String::sample()` --- src/modules/string/StringTest.cpp | 101 ++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/modules/string/StringTest.cpp b/src/modules/string/StringTest.cpp index 69c323d47..a420c0dc4 100644 --- a/src/modules/string/StringTest.cpp +++ b/src/modules/string/StringTest.cpp @@ -142,6 +142,107 @@ TEST_F(StringTest, shouldGenerateSampleString) { return static_cast(sampleCharacter) >= 33 && static_cast(sampleCharacter) <= 125; })); } +TEST_F(StringTest, shouldGenerateSampleStringWithGuarantee1) +{ + const auto sampleLength{20}; + // atleast 1 ';' - 3 ',' - 2 'a' + // atmost 3 ';' - 4 ',' - 10 'a' + const GuaranteeMap guarantee = {{';', {1, 3}}, {',', {3, 4}}, {'a', {2, 10}}}; + // it is a random function so lets test for 20 random generations + for (int i = 0; i < runCount; ++i) + { + auto copyGuarantee = guarantee; + const auto sample = String::sample(std::move(copyGuarantee), sampleLength); + + ASSERT_EQ(sample.size(), sampleLength); + ASSERT_TRUE(std::ranges::all_of( + sample, [](char sampleCharacter) + { return static_cast(sampleCharacter) >= 33 && static_cast(sampleCharacter) <= 125; })); + + auto count_semicolon = std::ranges::count(sample, ';'); + auto count_comma = std::ranges::count(sample, ','); + auto count_a = std::ranges::count(sample, 'a'); + + ASSERT_TRUE(count_semicolon >= 1 && count_semicolon <= 3); + ASSERT_TRUE(count_comma >= 3 && count_comma <= 4); + ASSERT_TRUE(count_a >= 2 && count_a <= 10); + } +} + +TEST_F(StringTest, shouldGenerateSampleStringWithGuarantee2) +{ + const auto sampleLength{20}; + // exactly 2 '@' + // atmost 1 '4' - 2 '5' - 3 'a' + const GuaranteeMap guarantee = {{'4', {0, 1}}, {'5', {0, 2}}, {'a', {0, 3}}, {'@', {2, 2}}}; + // it is a random function so lets test for 20 random generations + for (int i = 0; i < runCount; ++i) + { + auto copyGuarantee = guarantee; + const auto sample = String::sample(std::move(copyGuarantee), sampleLength); + + ASSERT_EQ(sample.size(), sampleLength); + ASSERT_TRUE(std::ranges::all_of( + sample, [](char sampleCharacter) + { return static_cast(sampleCharacter) >= 33 && static_cast(sampleCharacter) <= 125; })); + + auto count_4 = std::ranges::count(sample, '4'); + auto count_5 = std::ranges::count(sample, '5'); + auto count_a = std::ranges::count(sample, 'a'); + auto count_at = std::ranges::count(sample, '@'); + + ASSERT_TRUE(count_4 <= 1); + ASSERT_TRUE(count_5 <= 2); + ASSERT_TRUE(count_a <= 3); + ASSERT_TRUE(count_at == 2); + } +} + +TEST_F(StringTest, shouldGenerateSampleStringWithGuarantee3) +{ + const auto sampleLength{20}; + // atmost 4 '(' - 2 '{' - 1 '\' - 5 '/' + const GuaranteeMap guarantee = {{'(', {0, 4}}, {'{', {0, 2}}, {'\\', {0, 1}}, {'/', {0, 5}}}; + // it is a random function so lets test for 20 random generations + for (int i = 0; i < runCount; ++i) + { + auto copyGuarantee = guarantee; + const auto sample = String::sample(std::move(copyGuarantee), sampleLength); + + ASSERT_EQ(sample.size(), sampleLength); + ASSERT_TRUE(std::ranges::all_of( + sample, [](char sampleCharacter) + { return static_cast(sampleCharacter) >= 33 && static_cast(sampleCharacter) <= 125; })); + + auto count_leftBracket = std::ranges::count(sample, '('); + auto count_leftBrace = std::ranges::count(sample, '{'); + auto count_backSlash = std::ranges::count(sample, '\\'); + auto count_forwardSlash = std::ranges::count(sample, '/'); + + ASSERT_TRUE(count_leftBracket <= 4); + ASSERT_TRUE(count_leftBrace <= 2); + ASSERT_TRUE(count_backSlash <= 1); + ASSERT_TRUE(count_forwardSlash <= 5); + } +} + +TEST_F(StringTest, invalidGuaranteeForSample1) +{ + const auto sampleLength{20}; + // atleast 5 '3' - 6 ':' - 10 'A' // invalid // string will be atleast 21 which is wrong + // atmost 6 '3' + GuaranteeMap guarantee = {{'3', {5, 6}}, {':', {6}}, {'A', {10}}}; + ASSERT_THROW(String::sample(std::move(guarantee), sampleLength), std::invalid_argument); +} + +TEST_F(StringTest, invalidGuaranteeForSample2) +{ + const auto sampleLength{20}; + // exactly 2 '~' // invalid // not in char set + GuaranteeMap guarantee = {{'a', {3}}, {'A', {10}}, {'~', {2, 2}}}; + ASSERT_THROW(String::sample(std::move(guarantee), sampleLength), std::invalid_argument); +} + TEST_F(StringTest, shouldGenerateDefaultStringFromCharaters) { const std::string characters{"abc"};