1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-19 23:41:07 +01:00
nix/src/libutil-tests/sort.cc
John Ericson 01b2037bc0 Minimize the use of C Macros for characterization tests
Fewer macros is better!

Introduce a new `JsonChacterizationTest` mixin class to help with this.

Also, avoid some needless copies with `GetParam`.

Part of my effort shoring up the JSON formats with #13570.
2025-09-28 09:54:46 -04:00

274 lines
9.6 KiB
C++

#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "nix/util/sort.hh"
#include <vector>
#include <list>
#include <algorithm>
#include <random>
namespace nix {
struct MonotonicSubranges : public ::testing::Test
{
std::vector<int> empty_;
std::vector<int> basic_ = {1, 0, -1, -100, 10, 10, 20, 40, 5, 5, 20, 10, 10, 1, -5};
};
TEST_F(MonotonicSubranges, empty)
{
ASSERT_EQ(weaklyIncreasingPrefix(empty_.begin(), empty_.end()), empty_.begin());
ASSERT_EQ(weaklyIncreasingSuffix(empty_.begin(), empty_.end()), empty_.begin());
ASSERT_EQ(strictlyDecreasingPrefix(empty_.begin(), empty_.end()), empty_.begin());
ASSERT_EQ(strictlyDecreasingSuffix(empty_.begin(), empty_.end()), empty_.begin());
}
TEST_F(MonotonicSubranges, basic)
{
ASSERT_EQ(strictlyDecreasingPrefix(basic_.begin(), basic_.end()), basic_.begin() + 4);
ASSERT_EQ(strictlyDecreasingSuffix(basic_.begin(), basic_.end()), basic_.begin() + 12);
std::reverse(basic_.begin(), basic_.end());
ASSERT_EQ(weaklyIncreasingPrefix(basic_.begin(), basic_.end()), basic_.begin() + 5);
ASSERT_EQ(weaklyIncreasingSuffix(basic_.begin(), basic_.end()), basic_.begin() + 11);
}
template<typename T>
class SortTestPermutations : public ::testing::Test
{
std::vector<T> initialData = {std::numeric_limits<T>::max(), std::numeric_limits<T>::min(), 0, 0, 42, 126, 36};
std::vector<T> vectorData;
std::list<T> listData;
public:
std::vector<T> scratchVector;
std::list<T> scratchList;
std::vector<T> empty;
void SetUp() override
{
vectorData = initialData;
std::sort(vectorData.begin(), vectorData.end());
listData = std::list(vectorData.begin(), vectorData.end());
}
bool nextPermutation()
{
std::next_permutation(vectorData.begin(), vectorData.end());
std::next_permutation(listData.begin(), listData.end());
scratchList = listData;
scratchVector = vectorData;
return vectorData == initialData;
}
};
using SortPermutationsTypes = ::testing::Types<int, long long, short, unsigned, unsigned long>;
TYPED_TEST_SUITE(SortTestPermutations, SortPermutationsTypes);
TYPED_TEST(SortTestPermutations, insertionsort)
{
while (!this->nextPermutation()) {
auto & list = this->scratchList;
insertionsort(list.begin(), list.end());
ASSERT_TRUE(std::is_sorted(list.begin(), list.end()));
auto & vector = this->scratchVector;
insertionsort(vector.begin(), vector.end());
ASSERT_TRUE(std::is_sorted(vector.begin(), vector.end()));
}
}
TYPED_TEST(SortTestPermutations, peeksort)
{
while (!this->nextPermutation()) {
auto & vector = this->scratchVector;
peeksort(vector.begin(), vector.end());
ASSERT_TRUE(std::is_sorted(vector.begin(), vector.end()));
}
}
TEST(InsertionSort, empty)
{
std::vector<int> empty;
insertionsort(empty.begin(), empty.end());
}
struct RandomPeekSort : public ::testing::TestWithParam<
std::tuple</*maxSize*/ std::size_t, /*min*/ int, /*max*/ int, /*iterations*/ std::size_t>>
{
using ValueType = int;
std::vector<ValueType> data_;
std::mt19937 urng_;
std::uniform_int_distribution<int> distribution_;
void SetUp() override
{
const auto & [maxSize, min, max, iterations] = GetParam();
urng_ = std::mt19937(GTEST_FLAG_GET(random_seed));
distribution_ = std::uniform_int_distribution<int>(min, max);
}
auto regenerate()
{
const auto & [maxSize, min, max, iterations] = GetParam();
std::size_t dataSize = std::uniform_int_distribution<std::size_t>(0, maxSize)(urng_);
data_.resize(dataSize);
std::generate(data_.begin(), data_.end(), [&]() { return distribution_(urng_); });
}
};
TEST_P(RandomPeekSort, defaultComparator)
{
const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) {
regenerate();
peeksort(data_.begin(), data_.end());
ASSERT_TRUE(std::is_sorted(data_.begin(), data_.end()));
/* Sorting is idempotent */
peeksort(data_.begin(), data_.end());
ASSERT_TRUE(std::is_sorted(data_.begin(), data_.end()));
}
}
TEST_P(RandomPeekSort, greater)
{
const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) {
regenerate();
peeksort(data_.begin(), data_.end(), std::greater<int>{});
ASSERT_TRUE(std::is_sorted(data_.begin(), data_.end(), std::greater<int>{}));
/* Sorting is idempotent */
peeksort(data_.begin(), data_.end(), std::greater<int>{});
ASSERT_TRUE(std::is_sorted(data_.begin(), data_.end(), std::greater<int>{}));
}
}
TEST_P(RandomPeekSort, brokenComparator)
{
const auto & [maxSize, min, max, iterations] = GetParam();
/* This is a pretty nice way of modeling a worst-case scenario for a broken comparator.
If the sorting algorithm doesn't break in such case, then surely all deterministic
predicates won't break it. */
auto comp = [&]([[maybe_unused]] const auto & lhs, [[maybe_unused]] const auto & rhs) -> bool {
return std::uniform_int_distribution<unsigned>(0, 1)(urng_);
};
for (std::size_t i = 0; i < iterations; ++i) {
regenerate();
auto originalData = data_;
peeksort(data_.begin(), data_.end(), comp);
/* Check that the output is just a reordering of the input. This is the
contract of the implementation in regard to comparators that don't
define a strict weak order. */
std::sort(data_.begin(), data_.end());
std::sort(originalData.begin(), originalData.end());
ASSERT_EQ(originalData, data_);
}
}
TEST_P(RandomPeekSort, stability)
{
const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) {
regenerate();
std::vector<std::pair<int, int>> pairs;
/* Assign sequential ids to objects. After the sort ids for equivalent
elements should be in ascending order. */
std::transform(
data_.begin(), data_.end(), std::back_inserter(pairs), [id = std::size_t{0}](auto && val) mutable {
return std::pair{val, ++id};
});
auto comp = [&]([[maybe_unused]] const auto & lhs, [[maybe_unused]] const auto & rhs) -> bool {
return lhs.first > rhs.first;
};
peeksort(pairs.begin(), pairs.end(), comp);
ASSERT_TRUE(std::is_sorted(pairs.begin(), pairs.end(), comp));
for (auto begin = pairs.begin(), end = pairs.end(); begin < end; ++begin) {
auto key = begin->first;
auto innerEnd = std::find_if_not(begin, end, [key](const auto & lhs) { return lhs.first == key; });
ASSERT_TRUE(std::is_sorted(begin, innerEnd, [](const auto & lhs, const auto & rhs) {
return lhs.second < rhs.second;
}));
begin = innerEnd;
}
}
}
using RandomPeekSortParamType = RandomPeekSort::ParamType;
INSTANTIATE_TEST_SUITE_P(
PeekSort,
RandomPeekSort,
::testing::Values(
RandomPeekSortParamType{128, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 1024},
RandomPeekSortParamType{7753, -32, 32, 128},
RandomPeekSortParamType{11719, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 64},
RandomPeekSortParamType{4063, 0, 32, 256},
RandomPeekSortParamType{771, -8, 8, 2048},
RandomPeekSortParamType{433, 0, 1, 2048},
RandomPeekSortParamType{0, 0, 0, 1}, /* empty case */
RandomPeekSortParamType{
1, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 1}, /* single element */
RandomPeekSortParamType{
2, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 2}, /* two elements */
RandomPeekSortParamType{55425, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 128}));
template<typename T>
struct SortProperty : public ::testing::Test
{};
using SortPropertyTypes = ::testing::Types<int, unsigned, long long, short, std::string>;
TYPED_TEST_SUITE(SortProperty, SortPropertyTypes);
RC_GTEST_TYPED_FIXTURE_PROP(SortProperty, peeksortSorted, (std::vector<TypeParam> vec))
{
peeksort(vec.begin(), vec.end());
RC_ASSERT(std::is_sorted(vec.begin(), vec.end()));
}
RC_GTEST_TYPED_FIXTURE_PROP(SortProperty, peeksortSortedGreater, (std::vector<TypeParam> vec))
{
auto comp = std::greater<TypeParam>();
peeksort(vec.begin(), vec.end(), comp);
RC_ASSERT(std::is_sorted(vec.begin(), vec.end(), comp));
}
RC_GTEST_TYPED_FIXTURE_PROP(SortProperty, insertionsortSorted, (std::vector<TypeParam> vec))
{
insertionsort(vec.begin(), vec.end());
RC_ASSERT(std::is_sorted(vec.begin(), vec.end()));
}
RC_GTEST_PROP(SortProperty, peeksortStability, (std::vector<std::pair<char, char>> vec))
{
auto comp = [](auto lhs, auto rhs) { return lhs.first < rhs.first; };
auto copy = vec;
std::stable_sort(copy.begin(), copy.end(), comp);
peeksort(vec.begin(), vec.end(), comp);
RC_ASSERT(copy == vec);
}
RC_GTEST_TYPED_FIXTURE_PROP(SortProperty, peeksortSortedLinearComparisonComplexity, (std::vector<TypeParam> vec))
{
peeksort(vec.begin(), vec.end());
RC_ASSERT(std::is_sorted(vec.begin(), vec.end()));
std::size_t comparisonCount = 0;
auto countingComp = [&](auto lhs, auto rhs) {
++comparisonCount;
return lhs < rhs;
};
peeksort(vec.begin(), vec.end(), countingComp);
/* In the sorted case comparison complexify should be linear. */
RC_ASSERT(comparisonCount <= vec.size());
}
} // namespace nix