Skip to content

Commit

Permalink
Fixed a bug in RandomHelper::GetRandomMinMaxValue() to increase max_v…
Browse files Browse the repository at this point in the history
…alue by 1 to convert from "exclused" to "included" range.

Implementing re/seeding support for IRandomService.h
Created more unit tests. #146
  • Loading branch information
end2endzone committed Feb 1, 2024
1 parent 2f8ad09 commit eb35390
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/core/ActionProperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ namespace shellanything

// Validate pattern length against
size_t min_length = RandomHelper::GetNumericPatternLength(&random[0]);
std::string random_string = RandomHelper::GetRandomMinMaxValue(min_value, max_value);
std::string random_string = RandomHelper::GetRandomMinMaxValue(min_value, max_value + 1); // +1 to include max_value as a possible outcome.

bool must_have_leading_zeroes = (random[0] == RandomHelper::NUMERIC_DIGIT_WITH_LEADING_ZEROS_PATTERN);
if (must_have_leading_zeroes)
Expand Down
20 changes: 20 additions & 0 deletions src/core/IRandomService.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ namespace shellanything
/// <returns>Returns a valid uint32_t value.</returns>
virtual uint32_t GetRandomValue(uint32_t min_value, uint32_t max_value) = 0;

/// <summary>
/// Seed the random number generator with a real random value, if available.
/// </summary>
/// <returns>Returns true if the operation is succesful. Return false otherwise.</returns>
virtual bool Seed() = 0;

/// <summary>
/// Seed the random number generator with a custom 32 bit value.
/// </summary>
/// <param name="seed">The seed value.</param>
/// <returns>Returns true if the operation is succesful. Return false otherwise.</returns>
virtual bool Seed(uint32_t seed) = 0;

/// <summary>
/// Seed the random number generator with a custom 32 bit value.
/// </summary>
/// <param name="seed">The seed value.</param>
/// <returns>Returns true if the operation is succesful. Return false otherwise.</returns>
virtual bool Seed(uint64_t seed) = 0;

};

} //namespace shellanything
Expand Down
48 changes: 38 additions & 10 deletions src/core/PcgRandomService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,21 @@

namespace shellanything
{
PcgRandomService::PcgRandomService() :
mPimpl(NULL)
inline pcg32& GetPcg(void* pimpl)
{
mPimpl = new pcg32(42u, 54u);

// mPimpl to local pcg reference
pcg32& rng = *((pcg32*)(mPimpl));
pcg32& rng = *((pcg32*)(pimpl));

return rng;
}

PcgRandomService::PcgRandomService() :
mPimpl(NULL)
{
// Seed with a real random value, if available
rng.seed(pcg_extras::seed_seq_from<std::random_device>());
pcg_extras::seed_seq_from<std::random_device> seed_source;

mPimpl = new pcg32(seed_source);
}

PcgRandomService::~PcgRandomService()
Expand All @@ -51,21 +56,44 @@ namespace shellanything

uint32_t PcgRandomService::GetRandomValue()
{
// mPimpl to local pcg reference
pcg32& rng = *((pcg32*)(mPimpl));
pcg32& rng = GetPcg(mPimpl);

uint32_t value = rng();
return value;
}

uint32_t PcgRandomService::GetRandomValue(uint32_t min_value, uint32_t max_value)
{
// mPimpl to local pcg reference
pcg32& rng = *((pcg32*)(mPimpl));
pcg32& rng = GetPcg(mPimpl);

uint32_t range = max_value - min_value;
uint32_t value = rng(range) + min_value;
return value;
}

bool PcgRandomService::Seed()
{
pcg32& rng = GetPcg(mPimpl);

// Seed with a real random value, if available
pcg_extras::seed_seq_from<std::random_device> seed_source;

rng.seed(seed_source);
return true;
}

bool PcgRandomService::Seed(uint32_t seed)
{
pcg32& rng = GetPcg(mPimpl);
rng.seed(seed);
return true;
}

bool PcgRandomService::Seed(uint64_t seed)
{
pcg32& rng = GetPcg(mPimpl);
rng.seed(seed);
return true;
}

} //namespace shellanything
4 changes: 3 additions & 1 deletion src/core/PcgRandomService.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ namespace shellanything
virtual ~PcgRandomService();

virtual uint32_t GetRandomValue();

virtual uint32_t GetRandomValue(uint32_t min_value, uint32_t max_value);
virtual bool Seed();
virtual bool Seed(uint32_t seed);
virtual bool Seed(uint64_t seed);

private:
void * mPimpl;
Expand Down
2 changes: 2 additions & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ set(CONFIGURATION_TEST_FILES ""
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testCopyFile.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testFail.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testLiveProperties.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testRandomProperties.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testRandomPropertiesAdvanced.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testRegistryKey.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestActionProperty.testSearchPath.xml
${CMAKE_CURRENT_SOURCE_DIR}/test_files/TestConfigManager.testAssignCommandId.1.xml
Expand Down
180 changes: 180 additions & 0 deletions src/tests/TestActionProperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,186 @@ namespace shellanything
ASSERT_TRUE(workspace.Cleanup()) << "Failed deleting workspace directory '" << workspace.GetBaseDirectory() << "'.";
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionProperty, testRandomProperties)
{
ConfigManager& cmgr = ConfigManager::GetInstance();
PropertyManager& pmgr = PropertyManager::GetInstance();

// Seed the random number generator to get predictable values
IRandomService* random_service = App::GetInstance().GetRandomService();
ASSERT_TRUE(random_service != NULL);
uint32_t seed = 44u;
ASSERT_TRUE(random_service->Seed(seed));

//Creating a temporary workspace for the test execution.
Workspace workspace;
ASSERT_FALSE(workspace.GetBaseDirectory().empty());
ASSERT_TRUE(workspace.IsEmpty());

//Load the test Configuration File that matches this test name.
QuickLoader loader;
loader.SetWorkspace(&workspace);
ASSERT_TRUE(loader.DeleteConfigurationFilesInWorkspace());
ASSERT_TRUE(loader.LoadCurrentTestConfigurationFile());

//Get all menus.
ConfigFile::ConfigFilePtrList configs = cmgr.GetConfigFiles();
ASSERT_EQ(1, configs.size());

//ASSERT a multiple menus are available
Menu::MenuPtrList menus = cmgr.GetConfigFiles()[0]->GetMenus();
ASSERT_EQ(menus.size(), 7);

//Clear properties
static const char* properties[] = {
"test1",
"test2",
"test3",
"test4",
"test5",
"test6",
"test7",
};
static const size_t properties_count = sizeof(properties) / sizeof(properties[0]);
for (size_t i = 0; i < properties_count; i++)
{
pmgr.ClearProperty(properties[i]);
}

//Create a valid context
SelectionContext c;
StringList elements;
elements.push_back("C:\\Windows");
c.SetElements(elements);
c.RegisterProperties();

// Execute
for (size_t i = 0; i < menus.size(); i++)
{
Menu* menu = menus[i];
bool executed = ActionManager::Execute(menu, c);
ASSERT_TRUE(executed) << "Failed to execute actions of menu '" << menu->GetName() << "'.";
}

//ASSERT the properties were set
for (size_t i = 0; i < properties_count; i++)
{
const char* property_name = properties[i];
ASSERT_TRUE(pmgr.HasProperty(property_name)) << "Property not found: '" << property_name << "'.";
}

//ASSERT expected values for each properties
static const char* expected_values[] = {
"6564989",
"05509961",
"igrmoudd",
"MAEDMLOH",
"KXXlVh6Z",
"Pjl_(kF7",
"0056",
};
static const size_t expected_values_count = sizeof(expected_values) / sizeof(expected_values[0]);
ASSERT_EQ(expected_values_count, properties_count);
for (size_t i = 0; i < properties_count; i++)
{
const char* property_name = properties[i];
std::string actual_value = pmgr.GetProperty(property_name);
std::string expected_value = expected_values[i];
std::cout << property_name << "=`" << actual_value << "`.\n";
ASSERT_EQ(expected_value, actual_value);
}

//Cleanup
ASSERT_TRUE(workspace.Cleanup()) << "Failed deleting workspace directory '" << workspace.GetBaseDirectory() << "'.";
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionProperty, testRandomPropertiesAdvanced)
{
ConfigManager& cmgr = ConfigManager::GetInstance();
PropertyManager& pmgr = PropertyManager::GetInstance();

// Seed the random number generator to get predictable values
IRandomService* random_service = App::GetInstance().GetRandomService();
ASSERT_TRUE(random_service != NULL);
uint32_t seed = 2u;
ASSERT_TRUE(random_service->Seed(seed));

//Creating a temporary workspace for the test execution.
Workspace workspace;
ASSERT_FALSE(workspace.GetBaseDirectory().empty());
ASSERT_TRUE(workspace.IsEmpty());

//Load the test Configuration File that matches this test name.
QuickLoader loader;
loader.SetWorkspace(&workspace);
ASSERT_TRUE(loader.DeleteConfigurationFilesInWorkspace());
ASSERT_TRUE(loader.LoadCurrentTestConfigurationFile());

//Get all menus.
ConfigFile::ConfigFilePtrList configs = cmgr.GetConfigFiles();
ASSERT_EQ(1, configs.size());

//ASSERT a multiple menus are available
Menu::MenuPtrList menus = cmgr.GetConfigFiles()[0]->GetMenus();
ASSERT_EQ(menus.size(), 1);

//Clear properties
static const char* properties[] = {
"my_dice_roll",
"my_coin_flip",
"my_card",
};
static const size_t properties_count = sizeof(properties) / sizeof(properties[0]);
for (size_t i = 0; i < properties_count; i++)
{
pmgr.ClearProperty(properties[i]);
}
pmgr.Clear();
pmgr.RegisterLiveProperties();

//Create a valid context
SelectionContext c;
StringList elements;
elements.push_back("C:\\Windows");
c.SetElements(elements);
c.RegisterProperties();

// Execute
for (size_t i = 0; i < menus.size(); i++)
{
Menu* menu = menus[i];
bool executed = ActionManager::Execute(menu, c);
ASSERT_TRUE(executed) << "Failed to execute actions of menu '" << menu->GetName() << "'.";
}

//ASSERT the properties were set
for (size_t i = 0; i < properties_count; i++)
{
const char* property_name = properties[i];
ASSERT_TRUE(pmgr.HasProperty(property_name)) << "Property not found: '" << property_name << "'.";
}

//ASSERT expected values for each properties
static const char* expected_values[] = {
"3",
"Tails",
"Five of Diamonds",
};
static const size_t expected_values_count = sizeof(expected_values) / sizeof(expected_values[0]);
ASSERT_EQ(expected_values_count, properties_count);
for (size_t i = 0; i < properties_count; i++)
{
const char* property_name = properties[i];
std::string actual_value = pmgr.GetProperty(property_name);
std::string expected_value = expected_values[i];
std::cout << property_name << "=`" << actual_value << "`.\n";
ASSERT_EQ(expected_value, actual_value);
}

//Cleanup
ASSERT_TRUE(workspace.Cleanup()) << "Failed deleting workspace directory '" << workspace.GetBaseDirectory() << "'.";
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionProperty, testCaptureOutput)
{
ConfigManager& cmgr = ConfigManager::GetInstance();
Expand Down
30 changes: 17 additions & 13 deletions src/tests/TestRandomHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*********************************************************************************/

#include "TestRandomHelper.h"
#include "App.h"
#include "RandomHelper.h"

namespace shellanything
Expand Down Expand Up @@ -98,36 +99,39 @@ namespace shellanything
ASSERT_TRUE(RandomHelper::GetRandomFromPattern("", r));
ASSERT_FALSE(RandomHelper::GetRandomFromPattern("00bbb", r));

ASSERT_TRUE(RandomHelper::GetRandomFromPattern("#########", r));
std::cout << "r=" << r << "\n";
ASSERT_GT(r.size(), 2) << "r=" << r;
// Seed the random number generator to get predictable values
IRandomService* random_service = App::GetInstance().GetRandomService();
ASSERT_TRUE(random_service != NULL);
uint32_t seed = 44u;
ASSERT_TRUE(random_service->Seed(seed));

struct SINGLE_TEST
{
const char* pattern;
size_t length;
const char* expected_value;
};
static const SINGLE_TEST tests[] = {
{"0000", 4},
{"aaa", 3},
{"AA", 2},
{"zzZZzzZZ", 8},
{"*****", 5},
{"########", "6564989"},
{"00000000", "05509961"},
{"aaaaaaaa", "igrmoudd"},
{"AAAAAAAA", "MAEDMLOH"},
{"zzZZzzZZ", "KXXlVh6Z"},
{"********", "Pjl_(kF7"},
};
static const size_t num_tests = sizeof(tests) / sizeof(tests[0]);

// ASSERT each string has the expected size
// ASSERT each string generates the expected output
for (size_t i = 0; i < num_tests; i++)
{
const SINGLE_TEST& t = tests[i];
const char* pattern = t.pattern;
const size_t expected_length = t.length;
const std::string expected_value = t.expected_value;

bool success = RandomHelper::GetRandomFromPattern(pattern, r);
std::cout << "GetRandomFromPattern() with pattern `" << pattern << "` returns `" << r << "`.\n";

ASSERT_TRUE(success) << "pattern=" << pattern;
ASSERT_EQ(expected_length, r.size()) << "pattern=" << pattern << " , " << "r=" << r;
ASSERT_TRUE(success) << "pattern `" << pattern << "`.";
ASSERT_EQ(expected_value, r) << "pattern `" << pattern << "` returns `" << r << "`.";
}
}
//--------------------------------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit eb35390

Please sign in to comment.