138 lines
4.9 KiB
C++
138 lines
4.9 KiB
C++
|
/**
|
||
|
* Email: bt19ex@brocku.ca
|
||
|
* Student Number: 6920201
|
||
|
* Licence: GPL3.0 see https://www.gnu.org/licenses/gpl-3.0.en.html for more detail
|
||
|
* Basic random test case generator and tester with partially formatted output. Requires terminal unicode and ANSI escape code support for full effect.
|
||
|
*
|
||
|
* Please use Linux to run this software, tested on Debian stable using CLion 2023.2.2 // GCC version 12.2
|
||
|
* This should work on any linux distro with a C++20 compiler.
|
||
|
* CMake is optional as this file can be compiled via g++
|
||
|
* An compile command would be:
|
||
|
* `g++ -O3 -o main.run -Wall -Wpedantic -Wextra -Werror main.cpp && ./main.run`
|
||
|
* If you use CMake please make sure CMAKE_BUILD_TYPE is release otherwise the program will take a long time to run.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include <iostream>
|
||
|
#include <random>
|
||
|
#include <vector>
|
||
|
#include <cstdint>
|
||
|
#include <algorithm>
|
||
|
|
||
|
/**
|
||
|
* Config Variables
|
||
|
* ---------------------------------------
|
||
|
*/
|
||
|
static constexpr size_t DEFAULT_TEST_COUNT = 10; // Default: 10 tests
|
||
|
static constexpr size_t MAX_RAM_USAGE = 1ul * 1024ul * 1024ul; // Default: 1mb per test
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Do not change anything below this line!
|
||
|
* ---------------------------------------
|
||
|
*/
|
||
|
|
||
|
static constexpr auto maxSize = MAX_RAM_USAGE / sizeof(std::int32_t);
|
||
|
static const auto maxChars = std::to_string(maxSize).size();
|
||
|
|
||
|
struct failPoint
|
||
|
{
|
||
|
size_t i, j;
|
||
|
};
|
||
|
|
||
|
// fp has no meaning if the test doesn't fail.
|
||
|
bool validateTest(const std::vector<std::int32_t>& in, failPoint& fp)
|
||
|
{
|
||
|
// empty sets are sorted. if you have a problem with this take it up with god
|
||
|
if (in.empty())
|
||
|
return true;
|
||
|
for (size_t i = 0; i < in.size(); i++)
|
||
|
{
|
||
|
for (size_t j = i; j < in.size(); j++)
|
||
|
{
|
||
|
// if any value after the current value is less, then we have failed to sort.
|
||
|
if (in[i] > in[j])
|
||
|
{
|
||
|
fp = {i, j};
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
std::vector<std::int32_t> generateRandomData()
|
||
|
{
|
||
|
// setup random numbers
|
||
|
static std::random_device dev;
|
||
|
static std::mt19937_64 engine(dev());
|
||
|
// don't want the int array to be too big otherwise we'll run out of ram. you can change this for your machine but im on my laptop rn
|
||
|
// if it mattered I'd test it on my desktop with 4gb array allocations, but it doesn't
|
||
|
std::uniform_int_distribution dist(std::numeric_limits<std::int32_t>::min(), std::numeric_limits<std::int32_t>::max());
|
||
|
std::uniform_int_distribution dist_size(0ul, maxSize);
|
||
|
// pick a random size
|
||
|
size_t size = dist_size(engine);
|
||
|
// populate the array
|
||
|
std::vector<std::int32_t> ret;
|
||
|
for (size_t i = 0; i < size; i++)
|
||
|
ret.push_back(dist(engine));
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
template<typename T>
|
||
|
void runTest(T t, std::string name)
|
||
|
{
|
||
|
auto data = generateRandomData();
|
||
|
// little extra formatting goes a long way =3
|
||
|
name += "; Data Size: ";
|
||
|
auto dataSizeStr = std::to_string(data.size());
|
||
|
name += dataSizeStr;
|
||
|
|
||
|
// run the sort on the data
|
||
|
t(data);
|
||
|
|
||
|
// add padding for nice output :P
|
||
|
std::string padding;
|
||
|
for (size_t i = 0; i < maxChars - dataSizeStr.size(); i++)
|
||
|
padding += ' ';
|
||
|
|
||
|
failPoint fp{};
|
||
|
// input and output arrays cannot be shown due to their potential size. Instead, the software will output the failure value and locations.
|
||
|
// sometimes they are neighbours sometimes they are far apart. it depends on the randomness
|
||
|
if (validateTest(data, fp))
|
||
|
std::cout << "\033[36m[\033[32m✓\033[36m]:\033[0m Test '" << name << "'" << padding << "\t\033[32mPASSED\033[0m" << std::endl;
|
||
|
else
|
||
|
std::cout << "\033[36m[\033[31mX\033[36m]:\033[0m Test '" << name << "'" << padding << "\t\033[31mFAILED\t\t" << "Value '" << data[fp.i]
|
||
|
<< "' @ " << fp.i << " is greater than '" << data[fp.j] << "' @ " << fp.j << "\033[0m" << std::endl;
|
||
|
}
|
||
|
|
||
|
void brettSort(std::vector<std::int32_t>& v)
|
||
|
{
|
||
|
if (v.empty())
|
||
|
return;
|
||
|
static std::random_device dev;
|
||
|
static std::mt19937_64 engine(dev());
|
||
|
// since the STL won't fail (it's pretty good as long as you follow the rules)
|
||
|
std::sort(v.begin(), v.end());
|
||
|
// we can add some errors (sometimes) that will cause our test to fail (also sometimes)!
|
||
|
std::uniform_int_distribution<int> dist(0, (int) v.size() - 1);
|
||
|
std::uniform_real_distribution realDist(0.0, 1.0);
|
||
|
int p1 = dist(engine);
|
||
|
int p2 = dist(engine);
|
||
|
if (!v.empty() && realDist(engine) > 0.85)
|
||
|
v[p1] = v[p2];
|
||
|
}
|
||
|
|
||
|
int main(int argc, char** argv)
|
||
|
{
|
||
|
std::cout << "You can provide a test count via ./program [test count]" << std::endl;
|
||
|
auto testCount = DEFAULT_TEST_COUNT;
|
||
|
if (argc > 1)
|
||
|
testCount = std::stoi(argv[1]);
|
||
|
std::cout << "Running " << testCount << " tests" << std::endl;
|
||
|
// the brett sort should fail at some point
|
||
|
for (size_t i = 0; i < testCount; i++)
|
||
|
runTest(brettSort, "brettSort");
|
||
|
return 0;
|
||
|
}
|