diff --git a/tasks/tests/deque_test.cpp b/tasks/tests/deque_test.cpp index 04860e2..9fbbdf8 100644 --- a/tasks/tests/deque_test.cpp +++ b/tasks/tests/deque_test.cpp @@ -1,13 +1,16 @@ -#include -#include #include -#include -#include -#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include -//#include "deque.h" +// #include "deque.h" template using Deque = std::deque; @@ -15,715 +18,851 @@ using Deque = std::deque; namespace TestsByMesyarik { void test1() { - Deque d(10, 3); + Deque d(10, 3); - d[3] = 5; + d[3] = 5; - d[7] = 8; + d[7] = 8; - d[9] = 10; + d[9] = 10; - std::string s = "33353338310"; - std::string ss; - Deque dd; + std::string s = "33353338310"; + std::string ss; + Deque dd; - { - Deque d2 = d; + { + Deque d2 = d; - dd = d2; - } + dd = d2; + } - d[1] = 2; + d[1] = 2; - d.at(2) = 1; + d.at(2) = 1; - try { - d.at(10) = 0; - assert(false); - } catch (std::out_of_range&) {} + try { + d.at(10) = 0; + assert(false); + } catch (std::out_of_range&) {} - const Deque& ddd = dd; - for (size_t i = 0; i < ddd.size(); ++i) { - ss += std::to_string(ddd[i]); - } + const Deque& ddd = dd; + for (size_t i = 0; i < ddd.size(); ++i) { + ss += std::to_string(ddd[i]); + } - assert(s == ss); + assert(s == ss); } void test2() { - Deque d(1); + Deque d(1); - d[0] = 0; - - for (int i = 0; i < 8; ++i) { - d.push_back(i); - d.push_front(i); - } + d[0] = 0; - for (int i = 0; i < 12; ++i) { - d.pop_front(); - } - - d.pop_back(); - assert(d.size() == 4); + for (int i = 0; i < 8; ++i) { + d.push_back(i); + d.push_front(i); + } - std::string ss; + for (int i = 0; i < 12; ++i) { + d.pop_front(); + } - for (size_t i = 0; i < d.size(); ++i) { - ss += std::to_string(d[i]); - } - - assert(ss == "3456"); + d.pop_back(); + assert(d.size() == 4); + + std::string ss; + + for (size_t i = 0; i < d.size(); ++i) { + ss += std::to_string(d[i]); + } + + assert(ss == "3456"); } void test3() { - Deque d; - - for (int i = 0; i < 1000; ++i) { - for (int j = 0; j < 1000; ++j) { - - if (j % 3 == 2) { - d.pop_back(); - } else { - d.push_front(i*j); - } - - } + Deque d; + + for (int i = 0; i < 1000; ++i) { + for (int j = 0; j < 1000; ++j) { + if (j % 3 == 2) { + d.pop_back(); + } else { + d.push_front(i * j); + } } + } - assert(d.size() == 334'000); + assert(d.size() == 334'000); - Deque::iterator left = d.begin() + 100'000; - Deque::iterator right = d.end() - 233'990; - while (d.begin() != left) d.pop_front(); - while (d.end() != right) d.pop_back(); + Deque::iterator left = d.begin() + 100'000; + Deque::iterator right = d.end() - 233'990; + while (d.begin() != left) + d.pop_front(); + while (d.end() != right) + d.pop_back(); - assert(d.size() == 10); + assert(d.size() == 10); - assert(right - left == 10); + assert(right - left == 10); - std::string s; - for (auto it = left; it != right; ++it) { - ++*it; - } - for (auto it = right - 1; it >= left; --it) { - s += std::to_string(*it); - } - - assert(s == "51001518515355154401561015695158651595016120162051"); + std::string s; + for (auto it = left; it != right; ++it) { + ++*it; + } + for (auto it = right - 1; it >= left; --it) { + s += std::to_string(*it); + } + + assert(s == "51001518515355154401561015695158651595016120162051"); } struct S { - int x = 0; - double y = 0.0; + int x = 0; + double y = 0.0; }; void test4() { - - Deque d(5, {1, 2.0}); - const Deque& cd = d; + Deque d(5, {1, 2.0}); + const Deque& cd = d; - static_assert(!std::is_assignable_v); - static_assert(std::is_assignable_v); - static_assert(!std::is_assignable_v); + static_assert(!std::is_assignable_v); + static_assert(std::is_assignable_v); + static_assert(!std::is_assignable_v); - static_assert(!std::is_assignable_v); - static_assert(std::is_assignable_v); - static_assert(!std::is_assignable_v); + static_assert(!std::is_assignable_v); + static_assert(std::is_assignable_v); + static_assert(!std::is_assignable_v); - assert(cd.size() == 5); + assert(cd.size() == 5); - auto it = d.begin() + 2; - auto cit = cd.end() - 3; + auto it = d.begin() + 2; + auto cit = cd.end() - 3; - it->x = 5; - assert(cit->x == 5); + it->x = 5; + assert(cit->x == 5); - d.erase(d.begin() + 1); - d.erase(d.begin() + 3); - assert(d.size() == 3); + d.erase(d.begin() + 1); + d.erase(d.begin() + 3); + assert(d.size() == 3); - auto dd = cd; + auto dd = cd; - dd.pop_back(); - dd.insert(dd.begin(), {3, 4.0}); - dd.insert(dd.begin() + 2, {4, 5.0}); + dd.pop_back(); + dd.insert(dd.begin(), {3, 4.0}); + dd.insert(dd.begin() + 2, {4, 5.0}); - std::string s; - for (const auto& x: dd) { - s += std::to_string(x.x); - } - assert(s == "3145"); + std::string s; + for (const auto& x : dd) { + s += std::to_string(x.x); + } + assert(s == "3145"); - std::string ss; - for (const auto& x: d) { - ss += std::to_string(x.x); - } - assert(ss == "151"); + std::string ss; + for (const auto& x : d) { + ss += std::to_string(x.x); + } + assert(ss == "151"); } void test5() { - Deque d; - - d.push_back(1); - d.push_front(2); - - auto left_ptr = &*d.begin(); - auto right_ptr = &*(d.end()-1); - - d.push_back(3); - d.push_front(4); - auto left = *d.begin(); - auto right = *(d.end()-1); - - for (int i = 0; i < 10'000; ++i) { - d.push_back(i); - } - for (int i = 0; i < 20'000; ++i) { - d.push_front(i); - } - - std::string s; - s += std::to_string(left); - s += std::to_string(right); - - s += std::to_string(*left_ptr); - s += std::to_string(*right_ptr); - //for (auto it = left; it <= right; ++it) { - // s += std::to_string(*it); - //} - assert(s == "4321"); + Deque d; + + d.push_back(1); + d.push_front(2); + + auto left_ptr = &*d.begin(); + auto right_ptr = &*(d.end() - 1); + + d.push_back(3); + d.push_front(4); + auto left = *d.begin(); + auto right = *(d.end() - 1); + + for (int i = 0; i < 10'000; ++i) { + d.push_back(i); + } + for (int i = 0; i < 20'000; ++i) { + d.push_front(i); + } + + std::string s; + s += std::to_string(left); + s += std::to_string(right); + + s += std::to_string(*left_ptr); + s += std::to_string(*right_ptr); + // for (auto it = left; it <= right; ++it) { + // s += std::to_string(*it); + // } + assert(s == "4321"); } struct VerySpecialType { - int x = 0; + int x = 0; - explicit VerySpecialType(int x): x(x) {} + explicit VerySpecialType(int x) + : x(x) {} }; struct NotDefaultConstructible { - NotDefaultConstructible() = delete; - NotDefaultConstructible(const NotDefaultConstructible&) = default; - NotDefaultConstructible& operator=(const NotDefaultConstructible&) = default; + NotDefaultConstructible() = delete; + NotDefaultConstructible(const NotDefaultConstructible&) = default; + NotDefaultConstructible& operator=(const NotDefaultConstructible&) = default; - NotDefaultConstructible(VerySpecialType v): x(v.x) {} + NotDefaultConstructible(VerySpecialType v) + : x(v.x) {} public: - int x = 0; + int x = 0; }; void test6() { - Deque d; - - NotDefaultConstructible ndc = VerySpecialType(-1); + Deque d; - for (int i = 0; i < 1500; ++i) { - ++ndc.x; - d.push_back(ndc); - } + NotDefaultConstructible ndc = VerySpecialType(-1); - assert(d.size() == 1500); + for (int i = 0; i < 1500; ++i) { + ++ndc.x; + d.push_back(ndc); + } - for (int i = 0; i < 1300; ++i) { - d.pop_front(); - } + assert(d.size() == 1500); + + for (int i = 0; i < 1300; ++i) { + d.pop_front(); + } - assert(d.size() == 200); + assert(d.size() == 200); - assert(d[99].x == 1399); + assert(d[99].x == 1399); - d[100] = VerySpecialType(0); - assert(d[100].x == 0); + d[100] = VerySpecialType(0); + assert(d[100].x == 0); } struct Explosive { - int x = 0; - Explosive(int x): x(x) {} - Explosive(const Explosive&) { - if (x) throw std::runtime_error("Boom!"); - } + int x = 0; + Explosive(int x) + : x(x) {} + Explosive(const Explosive&) { + if (x) throw std::runtime_error("Boom!"); + } }; void test7() { + Deque d; + d.push_back(Explosive(0)); - Deque d; - d.push_back(Explosive(0)); - - for (int i = 0; i < 30'000; ++i) { - auto it = d.begin(); - auto x = it->x; - size_t sz = d.size(); - try { - if (i % 2) - d.push_back(Explosive(1)); - else - d.push_front(Explosive(1)); - } catch (...) { - assert(it == d.begin()); - assert(d.begin()->x == x); - assert(d.size() == sz); - } - - d.push_back(Explosive(0)); + for (int i = 0; i < 30'000; ++i) { + auto it = d.begin(); + auto x = it->x; + size_t sz = d.size(); + try { + if (i % 2) + d.push_back(Explosive(1)); + else + d.push_front(Explosive(1)); + } catch (...) { + assert(it == d.begin()); + assert(d.begin()->x == x); + assert(d.size() == sz); } + d.push_back(Explosive(0)); + } } -} // tests by mesyarik +} // namespace TestsByMesyarik namespace TestsByUnrealf1 { - struct Fragile { - Fragile(int durability, int data): durability(durability), data(data) {} - ~Fragile() = default; - - // for std::swap - Fragile(Fragile&& other): Fragile() { - *this = other; - } - - Fragile(const Fragile& other): Fragile() { - *this = other; - } - - Fragile& operator=(const Fragile& other) { - durability = other.durability - 1; - data = other.data; - if (durability <= 0) { - throw 2; - } - return *this; - } - - int durability; - int data; - private: - Fragile() { - - } - }; - - struct Explosive { - struct Safeguard {}; - - inline static bool exploded = false; - - Explosive(): should_explode(true) { - throw 1; - } - - Explosive(Safeguard): should_explode(false) { - - } - - Explosive(const Explosive&): should_explode(true) { - throw 2; - } - - //TODO: is this ok..? - Explosive& operator=(const Explosive&) {return *this;} - - ~Explosive() { - exploded |= should_explode; - } - - private: - const bool should_explode; - }; - - struct DefaultConstructible { - DefaultConstructible() { - data = default_data; - } - - int data = default_data; - inline static const int default_data = 117; - }; - - struct NotDefaultConstructible { - NotDefaultConstructible() = delete; - NotDefaultConstructible(int input): data(input) {} - int data; - - auto operator<=>(const NotDefaultConstructible&) const = default; - }; - - struct CountedException : public std::exception { - - }; - - template - struct Counted { - inline static int counter = 0; - - Counted() { - ++counter; - if (counter == when_throw) { - --counter; - throw CountedException(); - } - } - - Counted(const Counted&): Counted() { } - - ~Counted() { - --counter; - } - }; - - template - struct CheckIter{ - using traits = std::iterator_traits; - - static_assert(std::is_same_v, std::remove_cv_t>); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - static_assert(std::is_same_v()++), iter>); - static_assert(std::is_same_v()), iter&>); - static_assert(std::is_same_v() + 5), iter>); - static_assert(std::is_same_v() += 5), iter&>); - - static_assert(std::is_same_v() - std::declval()), typename traits::difference_type>); - static_assert(std::is_same_v()), T&>); - - static_assert(std::is_same_v() < std::declval()), bool>); - static_assert(std::is_same_v() <= std::declval()), bool>); - static_assert(std::is_same_v() > std::declval()), bool>); - static_assert(std::is_same_v() >= std::declval()), bool>); - static_assert(std::is_same_v() == std::declval()), bool>); - static_assert(std::is_same_v() != std::declval()), bool>); - }; - - void testDefault() { - Deque defaulted; - assert((defaulted.size() == 0)); - Deque without_default; - assert((without_default.size() == 0)); - } +struct Fragile { + Fragile(int durability, int data) + : durability(durability), + data(data) {} + ~Fragile() = default; + + // for std::swap + Fragile(Fragile&& other) + : Fragile() { + *this = other; + } + + Fragile(const Fragile& other) + : Fragile() { + *this = other; + } + + Fragile& operator=(const Fragile& other) { + durability = other.durability - 1; + data = other.data; + if (durability <= 0) { throw 2; } + return *this; + } + + int durability; + int data; + +private: + Fragile() {} +}; - void testCopy() { - Deque without_default; - Deque copy = without_default; - assert((copy.size() == 0)); - } +struct Explosive { + struct Safeguard {}; - void testWithSize() { - int size = 17; - int value = 14; - Deque simple(size); - assert((simple.size() == size_t(size)) && std::all_of(simple.begin(), simple.end(), [](int item){ return item == 0; })); - Deque less_simple(size, value); - assert((less_simple.size() == size_t(size)) && std::all_of(less_simple.begin(), less_simple.end(), [&](const auto& item){ - return item.data == value; - })); - Deque default_constructor(size); - assert(std::all_of(default_constructor.begin(), default_constructor.end(), [](const auto& item) { - return item.data == DefaultConstructible::default_data; - })); - } + inline static bool exploded = false; - void testAssignment(){ - Deque first(10, 10); - Deque second(9, 9); - first = second; - assert((first.size() == second.size()) && (first.size() == 9) && std::equal(first.begin(), first.end(), second.begin())); - } + Explosive() + : should_explode(true) { + throw 1; + } - void testStaticAsserts() { - using T1 = int; - using T2 = NotDefaultConstructible; - - static_assert(std::is_default_constructible_v>, "should have default constructor"); - static_assert(std::is_default_constructible_v>, "should have default constructor"); - static_assert(std::is_copy_constructible_v >, "should have copy constructor"); - static_assert(std::is_copy_constructible_v >, "should have copy constructor"); - //static_assert(std::is_constructible_v, int>, "should have constructor from int"); - //static_assert(std::is_constructible_v, int>, "should have constructor from int"); - static_assert(std::is_constructible_v, int, const T1&>, "should have constructor from int and const T&"); - static_assert(std::is_constructible_v, int, const T2&>, "should have constructor from int and const T&"); - - static_assert(std::is_copy_assignable_v>, "should have assignment operator"); - static_assert(std::is_copy_assignable_v>, "should have assignment operator"); - } - - void testOperatorSubscript() { - Deque defaulted(1300, 43); - assert((defaulted[0] == defaulted[1280]) && (defaulted[0] == 43)); - assert((defaulted.at(0) == defaulted[1280]) && (defaulted.at(0) == 43)); - int caught = 0; - - try { - defaulted.at(size_t(-1)); - } catch (std::out_of_range& e) { - ++caught; - } - - try { - defaulted.at(1300); - } catch (std::out_of_range& e) { - ++caught; - } - - assert(caught == 2); - } + Explosive(Safeguard) + : should_explode(false) {} - void testStaticAssertsAccess() { - Deque defaulted; - const Deque constant; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); + Explosive(const Explosive&) + : should_explode(true) { + throw 2; + } - //static_assert(noexcept(defaulted[0]), "operator[] should not throw"); - static_assert(!noexcept(defaulted.at(0)), "at() can throw"); + // TODO: is this ok..? + Explosive& operator=(const Explosive&) { return *this; } - } + ~Explosive() { exploded |= should_explode; } +private: + const bool should_explode; +}; - void testStaticAssertsIterators() { - CheckIter::iterator, int> iter; - std::ignore = iter; - CheckIter>().rbegin()), int> reverse_iter; - std::ignore = reverse_iter; - CheckIter>().cbegin()), const int> const_iter; - std::ignore = const_iter; - - static_assert(std::is_convertible_v< - decltype(std::declval>().begin()), - decltype(std::declval>().cbegin()) - >, "should be able to construct const iterator from non const iterator"); - static_assert(!std::is_convertible_v< - decltype(std::declval>().cbegin()), - decltype(std::declval>().begin()) - >, "should NOT be able to construct iterator from const iterator"); - } +struct DefaultConstructible { + DefaultConstructible() { data = default_data; } - void testIteratorsArithmetic() { - Deque empty; - assert((empty.end() - empty.begin()) == 0); - assert((empty.begin() + 0 == empty.end()) && (empty.end() - 0 == empty.begin())); + int data = default_data; + inline static const int default_data = 117; +}; - Deque one(1); - auto iter2 = one.end(); - assert(((--iter2) == one.begin())); +struct NotDefaultConstructible { + NotDefaultConstructible() = delete; + NotDefaultConstructible(int input) + : data(input) {} + int data; - assert((empty.rend() - empty.rbegin()) == 0); - assert((empty.rbegin() + 0 == empty.rend()) && (empty.rend() - 0 == empty.rbegin())); - auto r_iter = empty.rbegin(); - assert((r_iter++ == empty.rbegin())); + auto operator<=>(const NotDefaultConstructible&) const = default; +}; - assert((empty.cend() - empty.cbegin()) == 0); - assert((empty.cbegin() + 0 == empty.cend()) && (empty.cend() - 0 == empty.cbegin())); - auto c_iter = empty.cbegin(); - assert((c_iter++ == empty.cbegin())); +struct CountedException : public std::exception {}; - Deque d(1000, 3); - assert(size_t((d.end() - d.begin())) == d.size()); - assert((d.begin() + d.size() == d.end()) && (d.end() - d.size() == d.begin())); +template +struct Counted { + inline static int counter = 0; + + Counted() { + ++counter; + if (counter == when_throw) { + --counter; + throw CountedException(); } + } - void testIteratorsComparison() { - Deque d(1000, 3); + Counted(const Counted&) + : Counted() {} - assert(d.end() > d.begin()); - assert(d.cend() > d.cbegin()); - assert(d.rend() > d.rbegin()); - } + ~Counted() { --counter; } +}; - void testIteratorsAlgorithms() { - Deque d(1000, 3); - - std::iota(d.begin(), d.end(), 13); - std::mt19937 g(31415); - std::shuffle(d.begin(), d.end(), g); - std::sort(d.rbegin(), d.rbegin() + 500); - std::reverse(d.begin(), d.end()); - auto sorted_border = std::is_sorted_until(d.begin(), d.end()); - //std::copy(d.begin(), d.end(), std::ostream_iterator(std::cout, " ")); - //std::cout << std::endl; - assert(sorted_border - d.begin() == 500); - } +template +struct CheckIter { + using traits = std::iterator_traits; + + static_assert(std::is_same_v, std::remove_cv_t>); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert( + std::is_same_v); + + static_assert(std::is_same_v()++), iter>); + static_assert(std::is_same_v()), iter&>); + static_assert(std::is_same_v() + 5), iter>); + static_assert(std::is_same_v() += 5), iter&>); + + static_assert(std::is_same_v() - std::declval()), + typename traits::difference_type>); + static_assert(std::is_same_v()), T&>); + + static_assert(std::is_same_v() < std::declval()), bool>); + static_assert(std::is_same_v() <= std::declval()), bool>); + static_assert(std::is_same_v() > std::declval()), bool>); + static_assert(std::is_same_v() >= std::declval()), bool>); + static_assert(std::is_same_v() == std::declval()), bool>); + static_assert(std::is_same_v() != std::declval()), bool>); +}; - void testPushAndPop() { - Deque d(10000, { 1 }); - auto start_size = d.size(); - - auto middle = &(*(d.begin() + start_size / 2)); // 5000 - auto& middle_element = *middle; - auto begin = &(*d.begin()); - auto end = &(*d.rbegin()); - - auto middle2 = &(*((d.begin() + start_size / 2) + 2000)); // 7000 - - // remove 400 elements - for (size_t i = 0; i < 400; ++i) { - d.pop_back(); - } - - // begin and middle pointers are still valid - assert(begin->data == 1); - assert(middle->data == 1); - assert(middle_element.data == 1); - assert(middle2->data == 1); - - end = &*d.rbegin(); - - // 800 elemets removed in total - for (size_t i = 0; i < 400; ++i) { - d.pop_front(); - } - - // and and middle iterators are still valid - assert(end->data == 1); - assert(middle->data == 1); - assert(middle_element.data == 1); - assert(middle2->data == 1); - - // removed 9980 items in total - for (size_t i = 0; i < 4590; ++i) { - d.pop_front(); - d.pop_back(); - } - - assert(d.size() == 20); - assert(middle_element.data == 1); - assert(middle->data == 1 && middle->data == 1); - assert(std::all_of(d.begin(), d.end(), [](const auto& item) { return item.data == 1; } )); - - begin = &*d.begin(); - end = &*d.rbegin(); - - for (size_t i = 0; i < 5500; ++i) { - d.push_back({2}); - d.push_front({2}); - } - - assert((*begin).data == 1); - assert((*end).data == 1); - assert(d.begin()->data == 2); - assert(d.size() == 5500 * 2 + 20); - assert(std::count(d.begin(), d.end(), NotDefaultConstructible{1}) == 20); - assert(std::count(d.begin(), d.end(), NotDefaultConstructible{2}) == 11000); - } +void testDefault() { + Deque defaulted; + assert((defaulted.size() == 0)); + Deque without_default; + assert((without_default.size() == 0)); +} + +void testCopy() { + Deque without_default; + Deque copy = without_default; + assert((copy.size() == 0)); +} + +void testWithSize() { + int size = 17; + int value = 14; + Deque simple(size); + assert((simple.size() == size_t(size)) && + std::all_of(simple.begin(), simple.end(), [](int item) { return item == 0; })); + Deque less_simple(size, value); + assert((less_simple.size() == size_t(size)) && + std::all_of(less_simple.begin(), less_simple.end(), + [&](const auto& item) { return item.data == value; })); + Deque default_constructor(size); + assert(std::all_of(default_constructor.begin(), default_constructor.end(), [](const auto& item) { + return item.data == DefaultConstructible::default_data; + })); +} + +void testAssignment() { + Deque first(10, 10); + Deque second(9, 9); + first = second; + assert((first.size() == second.size()) && (first.size() == 9) && + std::equal(first.begin(), first.end(), second.begin())); +} - void testInsertAndErase() { - Deque d(10000, { 1 }); - auto start_size = d.size(); - - d.insert(d.begin() + start_size / 2, NotDefaultConstructible{2}); - assert(d.size() == start_size + 1); - d.erase(d.begin() + start_size / 2 - 1); - assert(d.size() == start_size); - - assert(size_t(std::count(d.begin(), d.end(), NotDefaultConstructible{1})) == start_size - 1); - assert(std::count(d.begin(), d.end(), NotDefaultConstructible{2}) == 1); - - Deque copy; - for (const auto& item : d) { - copy.insert(copy.end(), item); - } - // std::copy(d.cbegin(), d.cend(), std::inserter(copy, copy.begin())); - - assert(d.size() == copy.size()); - assert(std::equal(d.begin(), d.end(), copy.begin())); +void testStaticAsserts() { + using T1 = int; + using T2 = NotDefaultConstructible; + + static_assert(std::is_default_constructible_v>, "should have default constructor"); + static_assert(std::is_default_constructible_v>, "should have default constructor"); + static_assert(std::is_copy_constructible_v>, "should have copy constructor"); + static_assert(std::is_copy_constructible_v>, "should have copy constructor"); + // static_assert(std::is_constructible_v, int>, "should have constructor from int"); + // static_assert(std::is_constructible_v, int>, "should have constructor from int"); + static_assert(std::is_constructible_v, int, const T1&>, + "should have constructor from int and const T&"); + static_assert(std::is_constructible_v, int, const T2&>, + "should have constructor from int and const T&"); + + static_assert(std::is_copy_assignable_v>, "should have assignment operator"); + static_assert(std::is_copy_assignable_v>, "should have assignment operator"); +} + +void testOperatorSubscript() { + Deque defaulted(1300, 43); + assert((defaulted[0] == defaulted[1280]) && (defaulted[0] == 43)); + assert((defaulted.at(0) == defaulted[1280]) && (defaulted.at(0) == 43)); + int caught = 0; + + try { + defaulted.at(size_t(-1)); + } catch (std::out_of_range& e) { ++caught; } + + try { + defaulted.at(1300); + } catch (std::out_of_range& e) { ++caught; } + + assert(caught == 2); +} + +void testStaticAssertsAccess() { + Deque defaulted; + const Deque constant; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + // static_assert(noexcept(defaulted[0]), "operator[] should not throw"); + static_assert(!noexcept(defaulted.at(0)), "at() can throw"); +} + + +void testStaticAssertsIterators() { + CheckIter::iterator, int> iter; + std::ignore = iter; + CheckIter>().rbegin()), int> reverse_iter; + std::ignore = reverse_iter; + CheckIter>().cbegin()), const int> const_iter; + std::ignore = const_iter; + + static_assert(std::is_convertible_v>().begin()), + decltype(std::declval>().cbegin())>, + "should be able to construct const iterator from non const iterator"); + static_assert(!std::is_convertible_v>().cbegin()), + decltype(std::declval>().begin())>, + "should NOT be able to construct iterator from const iterator"); +} + +void testIteratorsArithmetic() { + Deque empty; + assert((empty.end() - empty.begin()) == 0); + assert((empty.begin() + 0 == empty.end()) && (empty.end() - 0 == empty.begin())); + + Deque one(1); + auto iter2 = one.end(); + assert(((--iter2) == one.begin())); + + assert((empty.rend() - empty.rbegin()) == 0); + assert((empty.rbegin() + 0 == empty.rend()) && (empty.rend() - 0 == empty.rbegin())); + auto r_iter = empty.rbegin(); + assert((r_iter++ == empty.rbegin())); + + assert((empty.cend() - empty.cbegin()) == 0); + assert((empty.cbegin() + 0 == empty.cend()) && (empty.cend() - 0 == empty.cbegin())); + auto c_iter = empty.cbegin(); + assert((c_iter++ == empty.cbegin())); + + Deque d(1000, 3); + assert(size_t((d.end() - d.begin())) == d.size()); + assert((d.begin() + d.size() == d.end()) && (d.end() - d.size() == d.begin())); +} + +void testIteratorsComparison() { + Deque d(1000, 3); + + assert(d.end() > d.begin()); + assert(d.cend() > d.cbegin()); + assert(d.rend() > d.rbegin()); +} + +void testIteratorsAlgorithms() { + Deque d(1000, 3); + + std::iota(d.begin(), d.end(), 13); + std::mt19937 g(31415); + std::shuffle(d.begin(), d.end(), g); + std::sort(d.rbegin(), d.rbegin() + 500); + std::reverse(d.begin(), d.end()); + auto sorted_border = std::is_sorted_until(d.begin(), d.end()); + // std::copy(d.begin(), d.end(), std::ostream_iterator(std::cout, " ")); + // std::cout << std::endl; + assert(sorted_border - d.begin() == 500); +} + +void testPushAndPop() { + Deque d(10000, {1}); + auto start_size = d.size(); + + auto middle = &(*(d.begin() + start_size / 2)); // 5000 + auto& middle_element = *middle; + auto begin = &(*d.begin()); + auto end = &(*d.rbegin()); + + auto middle2 = &(*((d.begin() + start_size / 2) + 2000)); // 7000 + + // remove 400 elements + for (size_t i = 0; i < 400; ++i) { + d.pop_back(); + } + + // begin and middle pointers are still valid + assert(begin->data == 1); + assert(middle->data == 1); + assert(middle_element.data == 1); + assert(middle2->data == 1); + + end = &*d.rbegin(); + + // 800 elemets removed in total + for (size_t i = 0; i < 400; ++i) { + d.pop_front(); + } + + // and and middle iterators are still valid + assert(end->data == 1); + assert(middle->data == 1); + assert(middle_element.data == 1); + assert(middle2->data == 1); + + // removed 9980 items in total + for (size_t i = 0; i < 4590; ++i) { + d.pop_front(); + d.pop_back(); + } + + assert(d.size() == 20); + assert(middle_element.data == 1); + assert(middle->data == 1 && middle->data == 1); + assert(std::all_of(d.begin(), d.end(), [](const auto& item) { return item.data == 1; })); + + begin = &*d.begin(); + end = &*d.rbegin(); + + for (size_t i = 0; i < 5500; ++i) { + d.push_back({2}); + d.push_front({2}); + } + + assert((*begin).data == 1); + assert((*end).data == 1); + assert(d.begin()->data == 2); + assert(d.size() == 5500 * 2 + 20); + assert(std::count(d.begin(), d.end(), NotDefaultConstructible{1}) == 20); + assert(std::count(d.begin(), d.end(), NotDefaultConstructible{2}) == 11000); +} + +void testInsertAndErase() { + Deque d(10000, {1}); + auto start_size = d.size(); + + d.insert(d.begin() + start_size / 2, NotDefaultConstructible{2}); + assert(d.size() == start_size + 1); + d.erase(d.begin() + start_size / 2 - 1); + assert(d.size() == start_size); + + assert(size_t(std::count(d.begin(), d.end(), NotDefaultConstructible{1})) == start_size - 1); + assert(std::count(d.begin(), d.end(), NotDefaultConstructible{2}) == 1); + + Deque copy; + for (const auto& item : d) { + copy.insert(copy.end(), item); + } + // std::copy(d.cbegin(), d.cend(), std::inserter(copy, copy.begin())); + + assert(d.size() == copy.size()); + assert(std::equal(d.begin(), d.end(), copy.begin())); +} + +void testExceptions() { + try { + Deque> d(100); + } catch (CountedException& e) { assert(Counted<17>::counter == 0); } catch (...) { + // should have caught same exception as thrown by Counted + assert(false); + } + + try { + Deque d(100); + } catch (...) {} + + try { + Deque d; + } catch (...) { + // no objects should have been created + assert(false); + } + assert(Explosive::exploded == false); + + try { + Deque d; + auto safe = Explosive(Explosive::Safeguard{}); + d.push_back(safe); + } catch (...) {} + + // Destructor should not be called for an object + // with no finihshed constructor + // the only destructor called - safe explosive with the safeguard + assert(Explosive::exploded == false); +} + +void testStrongGuarantee() { + const size_t size = 20'000; + const size_t initial_data = 100; + Deque d(size, Fragile(size, initial_data)); + + auto is_intact = [&] { + return d.size() == size && std::all_of(d.begin(), d.end(), [initial_data](const auto& item) { + return item.data == initial_data; + }); + }; + try { + d.insert(d.begin() + size / 2, Fragile(0, initial_data + 1)); + } catch (...) { + // have to throw + assert(is_intact()); + } + try { + // for those who like additional copies... + d.insert(d.begin() + size / 2, Fragile(3, initial_data + 2)); + } catch (...) { + // might throw depending on the implementation + // if it DID throw, then deque should be untouched + assert(is_intact()); + } +} +} // namespace TestsByUnrealf1 + +namespace TestsByDarkCodeForce { +struct Spectator { + static ssize_t balance; + + Spectator() { + assert(balance >= 0 && "More destructor calls than constructor calls"); + ++balance; + } + Spectator(const Spectator&) { + assert(balance >= 0 && "More destructor calls than constructor calls"); + ++balance; + } + Spectator(Spectator&&) { + assert(balance >= 0 && "More destructor calls than constructor calls"); + ++balance; + } + ~Spectator() { --balance; } + + Spectator& operator=(const Spectator&) { return *this; } + Spectator& operator=(Spectator&&) { return *this; }; +}; + +ssize_t Spectator::balance = 0; + +struct ExplosiveSpectatorError : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +struct ExplosiveSpectator : public Spectator { + mutable int delay; + +public: + ExplosiveSpectator() + : ExplosiveSpectator(-1) {} + + ExplosiveSpectator(int delay) + : Spectator(), + delay(delay) {} + + ExplosiveSpectator(const ExplosiveSpectator& other) + : Spectator(other), + delay(other.delay <= 0 ? other.delay : other.delay - 1) { + other.delay = delay; + if (!delay) { throw ExplosiveSpectatorError("KABOOM!"); } + } + + ExplosiveSpectator(ExplosiveSpectator&& other) + : Spectator(other), + delay(other.delay <= 0 ? other.delay : other.delay - 1) { + other.delay = delay; + if (!delay) { throw ExplosiveSpectatorError("KABOOM!"); } + } + + ~ExplosiveSpectator() = default; + +public: + ExplosiveSpectator& operator=(const ExplosiveSpectator& other) { + ExplosiveSpectator tmp(other); + delay = tmp.delay; + return *this; + } + + ExplosiveSpectator& operator=(ExplosiveSpectator&& other) { + ExplosiveSpectator tmp(other); + delay = tmp.delay; + return *this; + } +}; + +void testStability(ssize_t size) { + { + Deque d(size); + assert(Spectator::balance == size && "Constructors are not called in required quantity"); + d.pop_front(); + assert(Spectator::balance == size - 1 && "pop_front does not destroy object"); + d.pop_back(); + assert(Spectator::balance == size - 2 && "pop_back does not destroy object"); + d.push_front(Spectator()); + assert(Spectator::balance == size - 1 && "push_front does not construct object"); + d.push_back(Spectator()); + assert(Spectator::balance == size && "push_back does not construct object"); + } + assert(Spectator::balance == 0 && "Destructor does not properly destroy objects"); +} + +void testExceptionSafety(ssize_t size) { + { + Deque d(size); + try { + d.push_front(ExplosiveSpectator(0)); + assert(false && "push_front does not create new element"); + } catch (const ExplosiveSpectatorError&) { + assert(static_cast(d.size()) == size && Spectator::balance == size && + "push_front has bad exception guarantee"); + } + try { + d.push_back(ExplosiveSpectator(0)); + assert(false && "push_back does not create new element"); + } catch (const ExplosiveSpectatorError&) { + assert(static_cast(d.size()) == size && Spectator::balance == size && + "push_back has bad exception guarantee"); } - void testExceptions() { - try { - Deque> d(100); - } catch (CountedException& e) { - assert(Counted<17>::counter == 0); - } catch (...) { - // should have caught same exception as thrown by Counted - assert(false); - } - - try { - Deque d(100); - } catch (...) { - - } - - try { - Deque d; - } catch (...) { - // no objects should have been created - assert(false); - } - assert(Explosive::exploded == false); - - try { - Deque d; - auto safe = Explosive(Explosive::Safeguard{}); - d.push_back(safe); - } catch (...) { - - } - - // Destructor should not be called for an object - // with no finihshed constructor - // the only destructor called - safe explosive with the safeguard - assert(Explosive::exploded == false); + try { + Deque copy = d; + copy.push_back(ExplosiveSpectator()); + copy.rbegin()->delay = 0; + assert(static_cast(copy.size()) == size + 1 && Spectator::balance == size * 2 + 1); + + d = copy; + assert(false && "Not all elements are copied in operator="); + } catch (const ExplosiveSpectatorError&) { + assert(static_cast(d.size()) == size && Spectator::balance == size && + "operator= has bad exception guarantee"); } - void testStrongGuarantee() { - const size_t size = 20'000; - const size_t initial_data = 100; - Deque d(size, Fragile(size, initial_data)); - - auto is_intact = [&] { - return d.size() == size && std::all_of(d.begin(), d.end(), [initial_data](const auto& item) {return item.data == initial_data;} ); - }; - try { - d.insert(d.begin() + size / 2, Fragile(0, initial_data + 1)); - } catch (...) { - // have to throw - assert(is_intact()); - } - try { - // for those who like additional copies... - d.insert(d.begin() + size / 2, Fragile(3, initial_data + 2)); - } catch (...) { - // might throw depending on the implementation - // if it DID throw, then deque should be untouched - assert(is_intact()); - } + d.rbegin()->delay = 0; + try { + Deque copy = d; + assert(false && "Not all elements are copied in copy contructor"); + } catch (const ExplosiveSpectatorError&) { + assert(Spectator::balance == size && + "Copy constructor does not clear everything on exception"); } -} // namespace TestsByUnrealf1 + } + assert(Spectator::balance == 0 && "Destructor call ruined everything"); + + try { + Deque d(size, ExplosiveSpectator(size - 1)); + assert(false && "Contructor from int and object does not create all elements"); + } catch (const ExplosiveSpectatorError&) { + assert(Spectator::balance == 0 && "Constructor from int and object does not clear everything"); + } +} +} // namespace TestsByDarkCodeForce int main() { - - //static_assert(!std::is_same_v, - // Deque>, "You cannot use std::deque, cheater!"); - //static_assert(!std::is_base_of_v, - // Deque>, "You cannot use std::deque, cheater!"); - - TestsByMesyarik::test1(); - TestsByMesyarik::test2(); - TestsByMesyarik::test3(); - TestsByMesyarik::test4(); - TestsByMesyarik::test5(); - TestsByMesyarik::test6(); - TestsByMesyarik::test7(); - - TestsByUnrealf1::testDefault(); - TestsByUnrealf1::testCopy(); - TestsByUnrealf1::testWithSize(); - TestsByUnrealf1::testAssignment(); - TestsByUnrealf1::testStaticAsserts(); - TestsByUnrealf1::testOperatorSubscript(); - TestsByUnrealf1::testStaticAssertsAccess(); - TestsByUnrealf1::testStaticAssertsIterators(); - TestsByUnrealf1::testIteratorsArithmetic(); - TestsByUnrealf1::testIteratorsComparison(); - TestsByUnrealf1::testIteratorsAlgorithms(); - TestsByUnrealf1::testPushAndPop(); - TestsByUnrealf1::testInsertAndErase(); - TestsByUnrealf1::testExceptions(); - TestsByUnrealf1::testStrongGuarantee(); - - std::cout << 0; + // static_assert(!std::is_same_v, + // Deque>, "You cannot use std::deque, cheater!"); + // static_assert(!std::is_base_of_v, + // Deque>, "You cannot use std::deque, cheater!"); + + TestsByMesyarik::test1(); + TestsByMesyarik::test2(); + TestsByMesyarik::test3(); + TestsByMesyarik::test4(); + TestsByMesyarik::test5(); + TestsByMesyarik::test6(); + TestsByMesyarik::test7(); + + TestsByUnrealf1::testDefault(); + TestsByUnrealf1::testCopy(); + TestsByUnrealf1::testWithSize(); + TestsByUnrealf1::testAssignment(); + TestsByUnrealf1::testStaticAsserts(); + TestsByUnrealf1::testOperatorSubscript(); + TestsByUnrealf1::testStaticAssertsAccess(); + TestsByUnrealf1::testStaticAssertsIterators(); + TestsByUnrealf1::testIteratorsArithmetic(); + TestsByUnrealf1::testIteratorsComparison(); + TestsByUnrealf1::testIteratorsAlgorithms(); + TestsByUnrealf1::testPushAndPop(); + TestsByUnrealf1::testInsertAndErase(); + TestsByUnrealf1::testExceptions(); + TestsByUnrealf1::testStrongGuarantee(); + + std::vector sizes_to_test = { + 5, 8, 10, 16, 32, 64, 128, 256, 50}; // add your chuck size here if it is not present + + for (size_t size : sizes_to_test) { + TestsByDarkCodeForce::testStability(size); + TestsByDarkCodeForce::testExceptionSafety(size); + assert(TestsByDarkCodeForce::Spectator::balance >= 0 && + "More destructor calls than constructor calls"); + } + + std::cout << 0; } diff --git a/tasks/tests/tiny_test.hpp b/tasks/tests/tiny_test.hpp new file mode 100644 index 0000000..582c293 --- /dev/null +++ b/tasks/tests/tiny_test.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_source_location +# define USE_SOURCE_LOCATION +# endif // __cpp_lib_source_location +# endif // __has_include +#endif + + +template +constexpr auto type_name() { + std::string_view name, prefix, suffix; +#ifdef __clang__ + name = __PRETTY_FUNCTION__; + prefix = "auto type_name() [T = "; + suffix = "]"; +#elif defined(__GNUC__) + name = __PRETTY_FUNCTION__; + prefix = "constexpr auto type_name() [with T = "; + suffix = "]"; +#elif defined(_MSC_VER) + name = __FUNCSIG__; + prefix = "auto __cdecl type_name<"; + suffix = ">(void)"; +#endif + name.remove_prefix(prefix.size()); + name.remove_suffix(suffix.size()); + return name; +} + +namespace testing { + using namespace std::chrono_literals; + + template + concept Printable = requires(T item) { + { std::cout << item }; + }; + + class Test { + public: + Test(std::string name): name_(std::move(name)) {} + + virtual ~Test() = default; + + bool operator()() { + std::cout << "test \"" << name_ << "\"\n" << std::flush; + bool res = false; + try { + res = doTest(); + } catch(std::exception& exception) { + std::cout << "caught exception: " << exception.what() << '\n'; + } catch (...) { + std::cout << "caught unknown exception\n"; + } + std::cout << "[\x1B[" << (res ? "32mOK" : "31mFAIL") << "\033[0m]\n"; + return res; + } + + protected: + std::string name_; + + virtual bool doTest() = 0; + }; + + template + class SimpleTest: public Test { + public: + SimpleTest(std::string name, Functor f) + : Test(std::move(name)) + , f_(std::move(f)) {} + + bool doTest() override { + return f_(); + } + + private: + Functor f_; + }; + + template + class PrettyTest: public Test { + public: + PrettyTest(std::string name, Functor f) + : Test(std::move(name)) + , f_(std::move(f)) {} + + bool doTest() override { + f_(*this); + return result; + } + +#ifdef USE_SOURCE_LOCATION + bool check(bool condition, const std::source_location location = std::source_location::current()) { + result &= condition; + if (condition == false) { + std::cout + << "condition at " << location.file_name() + << ", line " << location.line() + << ':' << location.column() + << " evaluated to false\n"; + } + return condition; + } + + template + requires Printable && Printable + bool equals( + First& first, + Second&& second, + const std::source_location location = std::source_location::current()) { + const bool result = check(first == second, location); + if (!result) { + std::cout << first << " (" << type_name() << ") != " + << second << " (" << type_name() << ")\n"; + } + return result; + } + + bool equals( + auto&& first, + auto&& second, + const std::source_location location = std::source_location::current()) { + return check(first == second, location); + } + + bool fail(const std::source_location location = std::source_location::current()) { + return check(false, location); + } +#else + bool check(bool condition) { + result &= condition; + return condition; + } + + bool fail() { + return check(false); + } + + bool equals(auto&& first, auto&& second) { + return check(first == second); + } +#endif + + private: + Functor f_; + bool result = true; + }; + + template typename ActualTest, typename Functor> + std::unique_ptr> make_test( + std::string name, + Functor f) { + return std::make_unique>(std::move(name), std::move(f)); + } + + + template typename ActualTest, typename Functor> + struct TimedTest : ActualTest { + using Parent = ActualTest; + using Parent::Parent; + + template + TimedTest(double milliseconds, Args&&... args) + : Parent(std::forward(args)...) + , max_runtime_(milliseconds) {} + + bool doTest() override { + auto start = std::clock(); + auto result = Parent::doTest(); + auto finish = std::clock(); + double execution_ms = double(finish - start) * 1000.0 / CLOCKS_PER_SEC; + std::cout << "finished in " << std::setprecision(2) << execution_ms << "ms "; + if (max_runtime_ < execution_ms) { + std::cout << "(SLOW!)\n"; + return false; + } else { + std::cout << "(OK)\n"; + } + return result; + } + + private: + double max_runtime_ = std::numeric_limits::infinity(); + }; + + template typename ActualTest, typename Functor> + auto make_timed_test( + std::string name, + Functor f + ) { + return std::make_unique>(std::move(name), std::move(f)); + } + + template typename ActualTest, typename Functor> + auto make_timed_test( + std::chrono::milliseconds time_limit, + std::string name, + Functor f + ) { + return std::make_unique>(double(time_limit.count()), std::move(name), std::move(f)); + } + + + class TestGroup { + public: + TestGroup(const TestGroup&) = delete; + TestGroup(TestGroup&& other) = default; + TestGroup(std::string name): name_(std::move(name)) {} + + template + TestGroup(std::string name, FirstTest first, OtherTests... other) + : TestGroup(std::move(name) + , std::move(other)...) + { + add(std::move(first)); + } + + void add(std::unique_ptr test) { + tests_.push_back( std::move(test) ); + } + + bool run() { + std::cout << "Running group \"" << name_ << '\"' << std::endl; + size_t errors = 0; + for (auto& test : tests_) { + bool result = (*test)(); + if (!result) { + ++errors; + } + } + + bool result = (errors == 0); + if (!result) { + std::cout << "Group failed!\n"; + std::cout << "Failed " << errors << '/' << tests_.size() << " tests\n"; + } + return result; + } + + private: + std::string name_; + std::vector> tests_; + }; +} + diff --git a/tasks/tests/unordered_map_test.cpp b/tasks/tests/unordered_map_test.cpp index 83cf8f6..73a6db0 100644 --- a/tasks/tests/unordered_map_test.cpp +++ b/tasks/tests/unordered_map_test.cpp @@ -1,350 +1,378 @@ +// #include #include "unordered_map.h" -//#include -#include -#include -#include +#include #include - #include +#include +#include +#include +#include +#include +#include -/*template , - typename EqualTo = std::equal_to, - typename Alloc = std::allocator> > -using UnorderedMap = std::unordered_map; -*/ - -void SimpleTest() { - // std::cerr << "starting simple test" << std::endl; - UnorderedMap m; - - m["aaaaa"] = 5; - m["bbb"] = 6; - // std::cerr << "two first assignments with [] passed" << std::endl; - m.at("bbb") = 7; - // std::cerr << "assignment with at() passed" << std::endl; - assert(m.size() == 2); - - assert(m["aaaaa"] == 5); - assert(m["bbb"] == 7); - assert(m["ccc"] == 0); - - assert(m.size() == 3); - - // std::cerr << "assertions passed; before try block now" << std::endl; - - try { - m.at("xxxxxxxx"); - assert(false); - } catch (...) { - // std::cerr << "in catch block" << std::endl; - } - - auto it = m.find("dddd"); - assert(it == m.end()); - // std::cerr << "finding dddd passed, it == m.end()" << std::endl; - - it = m.find("bbb"); - assert(it->second == 7); - // std::cerr << "finding bbb passed, it->second == 7" << std::endl; - ++it->second; - assert(it->second == 8); - // std::cerr << "incrementing it->second passed, it->second==8" << std::endl; - - for (auto& item : m) { - --item.second; - } - assert(m.at("aaaaa") == 4); - // std::cerr << "decrementing each value passed, value at aaaaa == 4" << std::endl; - - { - auto mm = m; - // std::cerr << "m copied into mm" << std::endl; - m = std::move(mm); - // std::cerr << "mm moved into m" << std::endl; - } - // std::cerr << "copying and moving passed" << std::endl; - - auto res = m.emplace("abcde", 2); - assert(res.second); - // std::cerr << "emplacing abcde passed, value at abcde == 2" << std::endl; -} - -void TestIterators() { - UnorderedMap m; - - std::vector keys = {0.4, 0.3, -8.32, 7.5, 10.0, 0.0}; - std::vector values = { - "Summer has come and passed", - "The innocent can never last", - "Wake me up when September ends", - "Like my fathers come to pass", - "Seven years has gone so fast", - "Wake me up when September ends", - }; - - m.reserve(1'000'000); - - for (int i = 0; i < 6; ++i) { - m.insert({keys[i], values[i]}); - } - - auto beg = m.cbegin(); - std::string s = beg->second; - auto it = m.begin(); - ++it; - m.erase(it++); - it = m.begin(); - m.erase(++it); - - assert(beg->second == s); - assert(m.size() == 4); - - UnorderedMap mm; - std::vector> elements = { - {3.0, values[0]}, - {5.0, values[1]}, - {-10.0, values[2]}, - {35.7, values[3]} - }; - mm.insert(elements.cbegin(), elements.cend()); - s = mm.begin()->second; +#include "tiny_test.hpp" - m.insert(mm.begin(), mm.end()); - assert(mm.size() == 4); - assert(mm.begin()->second == s); +using testing::make_test; +using testing::PrettyTest; +using testing::TestGroup; +using groups_t = std::vector; +using namespace std::views; +namespace rng = std::ranges; +// template , +// typename EqualTo = std::equal_to, +// typename Alloc = std::allocator>> +// using UnorderedMap = std::unordered_map; - // Test traverse efficiency - m.reserve(1'000'000); // once again, nothing really should happen - assert(m.size() == 8); - // Actions below must be quick (~ 1000 * 8 operations) despite reserving space for 1M elements - for (int i = 0; i < 10000; ++i) { - long long h = 0; - for (auto it = m.cbegin(); it != m.cend(); ++it) { - // just some senseless action - h += int(it->first) + int((it->second)[0]); - } - std::ignore = h; - } - it = m.begin(); - ++it; - s = it->second; - // I asked to reserve space for 1M elements so actions below adding 100'000 elements mustn't cause reallocation - for (double d = 100.0; d < 10100.0; d += 0.1) { - m.emplace(d, "a"); - } - // And my iterator must point to the same object as before - assert(it->second == s); - - auto dist = std::distance(it, m.end()); - auto sz = m.size(); - m.erase(it, m.end()); - assert(sz - dist == m.size()); - - // Must be also fast - for (double d = 200.0; d < 10200.0; d += 0.35) { - auto it = m.find(d); - if (it != m.end()) m.erase(it); - } -} +namespace TestsByMesyarik { // Just a simple SFINAE trick to check CE presence when it's necessary // Stay tuned, we'll discuss this kind of tricks in our next lectures ;) -template -decltype(UnorderedMap().cbegin()->second = 0, int()) TestConstIteratorDoesntAllowModification(T) { - assert(false); +template +decltype(UnorderedMap().cbegin()->second = 0, int()) +TestConstIteratorDoesntAllowModification(T) { + assert(false); } -template +template void TestConstIteratorDoesntAllowModification(FakeArgs...) {} - struct VerySpecialType { - int x = 0; - explicit VerySpecialType(int x): x(x) {} - VerySpecialType(const VerySpecialType&) = delete; - VerySpecialType& operator=(const VerySpecialType&) = delete; - - VerySpecialType(VerySpecialType&&) = default; - VerySpecialType& operator=(VerySpecialType&&) = default; + int x = 0; + explicit VerySpecialType(int x) + : x(x) {} + VerySpecialType(const VerySpecialType&) = delete; + VerySpecialType& operator=(const VerySpecialType&) = delete; + + VerySpecialType(VerySpecialType&&) = default; + VerySpecialType& operator=(VerySpecialType&&) = default; }; struct NeitherDefaultNorCopyConstructible { - VerySpecialType x; + VerySpecialType x; - NeitherDefaultNorCopyConstructible() = delete; - NeitherDefaultNorCopyConstructible(const NeitherDefaultNorCopyConstructible&) = delete; - NeitherDefaultNorCopyConstructible& operator=(const NeitherDefaultNorCopyConstructible&) = delete; + NeitherDefaultNorCopyConstructible() = delete; + NeitherDefaultNorCopyConstructible(const NeitherDefaultNorCopyConstructible&) = delete; + NeitherDefaultNorCopyConstructible& operator=(const NeitherDefaultNorCopyConstructible&) = delete; - NeitherDefaultNorCopyConstructible(VerySpecialType&& x): x(std::move(x)) {} - NeitherDefaultNorCopyConstructible(NeitherDefaultNorCopyConstructible&&) = default; - NeitherDefaultNorCopyConstructible& operator=(NeitherDefaultNorCopyConstructible&&) = default; + NeitherDefaultNorCopyConstructible(VerySpecialType&& x) + : x(std::move(x)) {} + NeitherDefaultNorCopyConstructible(NeitherDefaultNorCopyConstructible&&) = default; + NeitherDefaultNorCopyConstructible& operator=(NeitherDefaultNorCopyConstructible&&) = default; - bool operator==(const NeitherDefaultNorCopyConstructible& other) const { - return x.x == other.x.x; - } + bool operator==(const NeitherDefaultNorCopyConstructible& other) const { + return x.x == other.x.x; + } }; +} // namespace TestsByMesyarik namespace std { - template<> - struct hash { - size_t operator()(const NeitherDefaultNorCopyConstructible& x) const { - return std::hash()(x.x.x); - } - }; -} - -void TestNoRedundantCopies() { -// std::cerr << "Test no redundant copies started" << std::endl; - UnorderedMap m; -// std::cerr << "m created" << std::endl; - m.reserve(10); -// std::cerr << "m.reserve(10) done" << std::endl; - m.emplace(VerySpecialType(0), VerySpecialType(0)); -// std::cerr << "m.emplace(VerySpecialType(0), VerySpecialType(0)) done" << std::endl; - m.reserve(1'000'000); -// std::cerr << "m.reserve(1000000) done" << std::endl; - std::pair p{VerySpecialType(1), VerySpecialType(1)}; - - m.insert(std::move(p)); -// std::cerr << "m.insert(std::move(p)) done" << std::endl; - - assert(m.size() == 2); - - // this shouldn't compile - // m[VerySpecialType(0)] = VerySpecialType(1); - - // but this should - m.at(VerySpecialType(1)) = VerySpecialType(0); -// std::cerr << "m.at(VerySpecialType(1)) = VerySpecialType(0) done" << std::endl; - - { - auto mm = std::move(m); -// std::cerr << "m moved to mm" << std::endl; - m = std::move(mm); -// std::cerr << "mm moved to m" << std::endl; - } -// std::cerr << "the scope of mm has finished" << std::endl; - m.erase(m.begin()); -// std::cerr << "m.erase(m.begin()) done" << std::endl; - m.erase(m.begin()); -// std::cerr << "m.erase(m.begin()) done once again" << std::endl; - assert(m.size() == 0); -} +template <> +struct hash { + size_t operator()(const TestsByMesyarik::NeitherDefaultNorCopyConstructible& x) const { + return std::hash()(x.x.x); + } +}; +} // namespace std +namespace TestsByMesyarik { -template +template struct MyHash { - size_t operator()(const T& p) const { - return std::hash()(p.second / p.first); - } + size_t operator()(const T& p) const { return std::hash()(p.second / p.first); } }; -template +template struct MyEqual { - bool operator()(const T& x, const T& y) const { - return y.second / y.first == x.second / x.first; - } + bool operator()(const T& x, const T& y) const { return y.second / y.first == x.second / x.first; } }; struct OneMoreStrangeStruct { - int first; - int second; + int first; + int second; }; bool operator==(const OneMoreStrangeStruct&, const OneMoreStrangeStruct&) = delete; +TestGroup create_basic_tests() { + return { + "Basic implementation tests", + make_test("simple", + [&](auto& test) { + // std::cerr << "starting simple test" << std::endl; + UnorderedMap m; + + m["aaaaa"] = 5; + m["bbb"] = 6; + // std::cerr << "two first assignments with [] passed" << + // std::endl; + m.at("bbb") = 7; + // std::cerr << "assignment with at() passed" << std::endl; + test.equals(m.size(), static_cast(2)); + + test.equals(m["aaaaa"], 5); + test.equals(m["bbb"], 7); + test.equals(m["ccc"], 0); + + test.equals(m.size(), static_cast(3)); + + // std::cerr << "assertions passed; before try block now" << + // std::endl; + + try { + m.at("xxxxxxxx"); + test.check(false); + } catch (...) { + // std::cerr << "in catch block" << std::endl; + } + + auto it = m.find("dddd"); + test.equals(it, m.end()); + // std::cerr << "finding dddd passed, it == m.end()" << std::endl; + + it = m.find("bbb"); + test.equals(it->second, 7); + // std::cerr << "finding bbb passed, it->second == 7" << + // std::endl; + ++it->second; + test.equals(it->second, 8); + // std::cerr << "incrementing it->second passed, it->second==8" << + // std::endl; + + for (auto& item : m) { + --item.second; + } + test.equals(m.at("aaaaa"), 4); + // std::cerr << "decrementing each value passed, value at aaaaa == + // 4" << std::endl; + + { + auto mm = m; + // std::cerr << "m copied into mm" << std::endl; + m = std::move(mm); + // std::cerr << "mm moved into m" << std::endl; + } + // std::cerr << "copying and moving passed" << std::endl; + + auto res = m.emplace("abcde", 2); + test.check(res.second); + // std::cerr << "emplacing abcde passed, value at abcde == 2" << + // std::endl; + }), + + make_test( + "iterators", + [&](auto& test) { + UnorderedMap m; + + std::vector keys = {0.4, 0.3, -8.32, 7.5, 10.0, 0.0}; + std::vector values = { + "Summer has come and passed", "The innocent can never last", + "Wake me up when September ends", "Like my fathers come to pass", + "Seven years has gone so fast", "Wake me up when September ends", + }; + + m.reserve(1'000'000); + + for (int i = 0; i < 6; ++i) { + m.insert({keys[i], values[i]}); + } -void TestCustomHashAndCompare() { - UnorderedMap, char, MyHash>, - MyEqual>> m; - - m.insert({{1, 2}, 0}); - m.insert({{2, 4}, 1}); - assert(m.size() == 1); - - m[{3, 6}] = 3; - assert(m.at({4, 8}) == 3); - - UnorderedMap, MyEqual> mm; - mm[{1, 2}] = 3; - assert(mm.at({5, 10}) == 3); + auto beg = m.cbegin(); + std::string s = beg->second; + auto it = m.begin(); + ++it; + m.erase(it++); + it = m.begin(); + m.erase(++it); + + test.equals(beg->second, s); + test.equals(m.size(), static_cast(4)); + + UnorderedMap mm; + std::vector> elements = { + {3.0, values[0]}, {5.0, values[1]}, {-10.0, values[2]}, {35.7, values[3]}}; + mm.insert(elements.cbegin(), elements.cend()); + s = mm.begin()->second; + + m.insert(mm.begin(), mm.end()); + test.equals(mm.size(), static_cast(4)); + test.equals(mm.begin()->second, s); + + + // Test traverse efficiency + m.reserve(1'000'000); // once again, nothing really should happen + test.equals(m.size(), static_cast(8)); + // Actions below must be quick (~ 1000 * 8 operations) despite reserving space for 1M + // elements + for (int i = 0; i < 10000; ++i) { + long long h = 0; + for (auto it = m.cbegin(); it != m.cend(); ++it) { + // just some senseless action + h += int(it->first) + int((it->second)[0]); + } + std::ignore = h; + } - mm.emplace(OneMoreStrangeStruct{3, 9}, 2); - assert(mm.size() == 2); - mm.reserve(1'000); - mm.erase(mm.begin()); - mm.erase(mm.begin()); - for (int k = 1; k < 100; ++k) { - for (int i = 1; i < 10; ++i) { - mm.insert({{i, k*i}, 0}); + it = m.begin(); + ++it; + s = it->second; + // I asked to reserve space for 1M elements so actions below adding 100'000 elements + // mustn't cause reallocation + for (double d = 100.0; d < 10100.0; d += 0.1) { + m.emplace(d, "a"); } - } - std::string ans; - std::string myans; - for (auto it = mm.cbegin(); it != mm.cend(); ++it) { - ans += std::to_string(it->second); - myans += '0'; - } - assert(ans == myans); + // And my iterator must point to the same object as before + test.equals(it->second, s); + + auto dist = std::distance(it, m.end()); + auto sz = m.size(); + m.erase(it, m.end()); + test.equals(sz - dist, m.size()); + + // Must be also fast + for (double d = 200.0; d < 10200.0; d += 0.35) { + auto it = m.find(d); + if (it != m.end()) m.erase(it); + } + }), + + make_test( + "no redundant copies", + [&](auto& test) { + // std::cerr << "Test no redundant copies started" << std::endl; + UnorderedMap m; + // std::cerr << "m created" << std::endl; + m.reserve(10); + // std::cerr << "m.reserve(10) done" << std::endl; + m.emplace(VerySpecialType(0), VerySpecialType(0)); + // std::cerr << "m.emplace(VerySpecialType(0), VerySpecialType(0)) done" << std::endl; + m.reserve(1'000'000); + // std::cerr << "m.reserve(1000000) done" << std::endl; + std::pair p{ + VerySpecialType(1), VerySpecialType(1)}; + + m.insert(std::move(p)); + // std::cerr << "m.insert(std::move(p)) done" << std::endl; + + test.equals(m.size(), static_cast(2)); + + // this shouldn't compile + // m[VerySpecialType(0)] = VerySpecialType(1); + + // but this should + m.at(VerySpecialType(1)) = VerySpecialType(0); + // std::cerr << "m.at(VerySpecialType(1)) = VerySpecialType(0) done" << std::endl; + + { + auto mm = std::move(m); + // std::cerr << "m moved to mm" << std::endl; + m = std::move(mm); + // std::cerr << "mm moved to m" << std::endl; + } + // std::cerr << "the scope of mm has finished" << std::endl; + m.erase(m.begin()); + // std::cerr << "m.erase(m.begin()) done" << std::endl; + m.erase(m.begin()); + // std::cerr << "m.erase(m.begin()) done once again" << std::endl; + test.equals(m.size(), static_cast(0)); + }), + make_test("custom hash and compare", + [&](auto& test) { + UnorderedMap, char, MyHash>, + MyEqual>> + m; + + m.insert({{1, 2}, 0}); + m.insert({{2, 4}, 1}); + test.equals(m.size(), static_cast(1)); + + m[{3, 6}] = 3; + test.equals(m.at({4, 8}), 3); + + UnorderedMap, + MyEqual> + mm; + mm[{1, 2}] = 3; + test.equals(mm.at({5, 10}), 3); + + mm.emplace(OneMoreStrangeStruct{3, 9}, 2); + test.equals(mm.size(), static_cast(2)); + mm.reserve(1'000); + mm.erase(mm.begin()); + mm.erase(mm.begin()); + for (int k = 1; k < 100; ++k) { + for (int i = 1; i < 10; ++i) { + mm.insert({{i, k * i}, 0}); + } + } + std::string ans; + std::string myans; + for (auto it = mm.cbegin(); it != mm.cend(); ++it) { + ans += std::to_string(it->second); + myans += '0'; + } + test.equals(ans, myans); + }) + + }; } - // Finally, some tricky fixtures to test custom allocator. // Done by professional, don't try to repeat class Chaste { private: - int x = 0; - Chaste() = default; - Chaste(int x): x(x) {} + int x = 0; + Chaste() = default; + Chaste(int x) + : x(x) {} - // Nobody can construct me except this guy - template - friend struct TheChosenOne; + // Nobody can construct me except this guy + template + friend struct TheChosenOne; public: - Chaste(const Chaste&) = default; - Chaste(Chaste&&) = default; + Chaste(const Chaste&) = default; + Chaste(Chaste&&) = default; - bool operator==(const Chaste& other) const { - return x == other.x; - } + bool operator==(const Chaste& other) const { return x == other.x; } }; +} // namespace TestsByMesyarik namespace std { - template<> - struct hash { - size_t operator()(const Chaste& x) const noexcept { - return std::hash()(reinterpret_cast(x)); - } - }; -} +template <> +struct hash { + size_t operator()(const TestsByMesyarik::Chaste& x) const noexcept { + return std::hash()(reinterpret_cast(x)); + } +}; +} // namespace std -template -struct TheChosenOne: public std::allocator { - TheChosenOne() {} +namespace TestsByMesyarik { - template - TheChosenOne(const TheChosenOne&) {} +template +struct TheChosenOne : public std::allocator { + TheChosenOne() {} - template - void construct(T* p, Args&&... args) const { - new(p) T(std::forward(args)...); - } + template + TheChosenOne(const TheChosenOne&) {} - void construct(std::pair* p, int a, int b) const { - new(p) std::pair(Chaste(a), Chaste(b)); - } + template + void construct(T* p, Args&&... args) const { + new (p) T(std::forward(args)...); + } - void destroy(T* p) const { - p->~T(); - } + void construct(std::pair* p, int a, int b) const { + new (p) std::pair(Chaste(a), Chaste(b)); + } - template - struct rebind { - using other = TheChosenOne; - }; + void destroy(T* p) const { p->~T(); } + + template + struct rebind { + using other = TheChosenOne; + }; }; /* @@ -373,43 +401,550 @@ struct TheChosenOne> }; */ -void TestCustomAlloc() { - // This container mustn't construct or destroy any objects without using TheChosenOne allocator - UnorderedMap, std::equal_to, - TheChosenOne>> m; - - m.emplace(0, 0); - - { - auto mm = m; - mm.reserve(1'000); - mm.erase(mm.begin()); - } +TestGroup create_special_allocator_tests() { + return {"Special allocator tests", make_test("special allocator", [&](auto&) { + // This container mustn't construct or destroy any objects without using TheChosenOne + // allocator + UnorderedMap, std::equal_to, + TheChosenOne>> + m; + + m.emplace(0, 0); + + { + auto mm = m; + mm.reserve(1'000); + mm.erase(mm.begin()); + } + + for (int i = 0; i < 1'000'000; ++i) { + m.emplace(i, i); + } + + for (int i = 0; i < 500'000; ++i) { + auto it = m.begin(); + ++it, ++it; + m.erase(m.begin(), it); + } + })}; +} +} // namespace TestsByMesyarik - for (int i = 0; i < 1'000'000; ++i) { - m.emplace(i, i); - } +namespace TestsByUnrealf1 { +struct Data { + int data; - for (int i = 0; i < 500'000; ++i) { - auto it = m.begin(); - ++it, ++it; - m.erase(m.begin(), it); - } + auto operator<=>(const Data&) const = default; +}; + +struct Trivial : Data {}; +constexpr Trivial operator""_tr(unsigned long long int x) { return Trivial{int(x)}; } + +struct NonTrivial : Data { + NonTrivial() {} + NonTrivial(int x) { data = x; } +}; +NonTrivial operator""_ntr(unsigned long long int x) { return NonTrivial{int(x)}; } + +struct NotDefaultConstructible { + NotDefaultConstructible() = delete; + NotDefaultConstructible(int input) + : data(input) {} + int data; + + auto operator<=>(const NotDefaultConstructible&) const = default; +}; + +struct NeitherDefaultNorCopyConstructible { + NeitherDefaultNorCopyConstructible() = delete; + NeitherDefaultNorCopyConstructible(const NeitherDefaultNorCopyConstructible&) = delete; + NeitherDefaultNorCopyConstructible(NeitherDefaultNorCopyConstructible&& other) + : data(other.data), + moved(true){}; + NeitherDefaultNorCopyConstructible(int input) + : data(input), + moved(false) {} + int data; + const bool moved = false; + + auto operator<=>(const NeitherDefaultNorCopyConstructible&) const = default; +}; + +template +struct NotPropagatedCountingAllocator { + size_t allocates_counter = 0; + size_t deallocates_counter = 0; + + using propagate_on_container_move_assignment = std::false_type; + using value_type = T; + + NotPropagatedCountingAllocator() = default; + + template + NotPropagatedCountingAllocator(const NotPropagatedCountingAllocator& other) + : allocates_counter(other.allocates_counter), + deallocates_counter(other.deallocates_counter) {} + + template + NotPropagatedCountingAllocator(NotPropagatedCountingAllocator&& other) + : allocates_counter(other.allocates_counter), + deallocates_counter(other.deallocates_counter) { + other.allocates_counter = 0; + other.deallocates_counter = 0; + } + + bool operator==(const NotPropagatedCountingAllocator&) const { return false; } + + T* allocate(size_t n) { + ++allocates_counter; + return std::allocator().allocate(n); + } + + void deallocate(T* pointer, size_t n) { + ++deallocates_counter; + return std::allocator().deallocate(pointer, n); + } +}; + +const size_t small_size = 17; +const size_t medium_size = 100; + +constexpr size_t operator""_sz(unsigned long long int x) { return size_t(x); } + +template +auto make_small_map() { + UnorderedMap map; + for (int i = 0; i < int(small_size); ++i) { + map.emplace(i, Value{i}); + } + return map; } +bool maps_equal(const auto& left, const auto& right) { + return rng::all_of(left, [&right](const auto& pr) { return right.at(pr.first) == pr.second; }); +} + +// NOLINTNEXTLINE +TestGroup create_constructor_tests() { + return { + "construction and assignment", + make_test("default", + [](auto& test) { + UnorderedMap defaulted; + test.equals(defaulted.size(), 0_sz); + UnorderedMap without_default; + test.equals(without_default.size(), 0_sz); + }), + + make_test("copy and move", + [&](auto& test) { + { + auto map = make_small_map(); + auto copy = map; + test.check(maps_equal(copy, map)); + auto move_copy = std::move(map); + test.check(maps_equal(copy, move_copy)); + test.equals(map.size(), 0_sz); + } + { + auto map = make_small_map(); + auto copy = map; + test.check(maps_equal(copy, map)); + auto move_copy = std::move(map); + test.check(maps_equal(copy, move_copy)); + test.equals(map.size(), 0_sz); + } + }), + + make_test("assignment operators", + [](auto& test) { + auto map = make_small_map(); + test.equals(map.size(), small_size); + UnorderedMap map2; + test.equals(map2.size(), 0_sz); + + map2 = map; + test.check(maps_equal(map, map2)); + map2 = std::move(map); + test.equals(map.size(), 0_sz); + test.equals(map2.size(), small_size); + }), + + make_test( + "move assignment with unequal and not propagating allocator", + [](auto& test) { + using custom_alloc_type = + NotPropagatedCountingAllocator>; + using special_map_type = + UnorderedMap, std::equal_to, + custom_alloc_type>; + special_map_type map; + map.emplace(1, NeitherDefaultNorCopyConstructible(1)); + auto address1 = std::addressof(*map.begin()); + special_map_type map_moved; + test.check(map_moved.get_allocator() != map.get_allocator()); + map_moved = std::move(map); + auto address2 = std::addressof(*map_moved.begin()); + test.check(map_moved.at(1).moved); + test.check(address1 != address2); + }), + + make_test("swap", + [](auto& test) { + auto map = make_small_map(); + decltype(map) another; + auto it = map.find(1); + auto address = &(*it); + test.equals(it->second, 1_tr); + map.swap(another); + test.equals(it->second, 1_tr); + test.equals(address->second, 1_tr); + }) + + }; +} + +// NOLINTNEXTLINE +TestGroup create_modification_tests() { + return { + "modification", + make_test("emplace", + [](auto& test) { + UnorderedMap map; + auto [place, did_insert] = map.emplace(1, 1_ntr); + test.equals(map.at(1), 1_ntr); + test.equals(place, map.begin()); + test.check(did_insert); + auto [new_place, new_did_insert] = map.emplace(2, 2_ntr); + // update place as it could be invalidated by rehash + place = map.find(1); + test.check(place != new_place); + test.check(new_did_insert); + test.equals(map.at(2), 2_ntr); + test.equals(map.at(1), 1_ntr); + auto [old_place, reinsert] = map.emplace(1, 3_ntr); + test.check(!reinsert); + test.equals(old_place, place); + test.equals(map.at(1), 1_ntr); + test.equals(map.at(2), 2_ntr); + }), + + make_test("emplace move", + [](auto& test) { + UnorderedMap moving_map; + std::string a = "a"; + std::string b = "b"; + std::string c = "c"; + moving_map.emplace(a, a); + test.equals(a, "a"); + moving_map.emplace(std::move(b), a); + test.equals(a, "a"); + test.equals(b, ""); + moving_map.emplace(std::move(c), std::move(a)); + test.equals(a, ""); + test.equals(c, ""); + test.equals(moving_map.size(), 3_sz); + test.equals(moving_map.at("a"), "a"); + test.equals(moving_map.at("b"), "a"); + test.equals(moving_map.at("c"), "a"); + }), + + make_test("insert nontrivial", + [](auto& test) { + UnorderedMap map; + auto [place, did_insert] = map.insert({1, 1_ntr}); + test.equals(map.at(1), 1_ntr); + test.equals(place, map.begin()); + test.check(did_insert); + auto [new_place, new_did_insert] = map.insert({2, 2_ntr}); + place = map.find(1); + test.check(place != new_place); + test.check(new_did_insert); + test.equals(map.at(2), 2_ntr); + test.equals(map.at(1), 1_ntr); + auto [old_place, reinsert] = map.insert({1, 3_ntr}); + test.check(!reinsert); + test.equals(old_place, place); + test.equals(map.at(1), 1_ntr); + test.equals(map.at(2), 2_ntr); + }), + + make_test("insert move", + [](auto& test) { + UnorderedMap moving_map; + using node = std::pair; + + node a{"a", "a"}; + node b{"b", "b"}; + + moving_map.insert(a); + test.equals("a", a.first); + test.equals(moving_map.size(), 1_sz); + + moving_map.insert(std::move(b)); + test.equals("", b.first); + test.equals(moving_map.size(), 2_sz); + + test.equals(moving_map.at("a"), "a"); + test.equals(moving_map.at("b"), "b"); + }), + + make_test("insert range", + [](auto& test) { + UnorderedMap map; + /*auto range = iota(0, int(medium_size)) + | transform([](int item) -> std::pair { + return {item, {item}}; } ) | common;*/ + std::vector> range; + for (int i = 0; i < int(medium_size); ++i) { + range.emplace_back(i, NonTrivial{i}); + } + map.insert(range.begin(), range.end()); + /*test.check(rng::all_of(iota(0, int(medium_size)), [&](int item) { + return map.at(item) == NonTrivial{item}; + }));*/ + std::vector indices(small_size); + std::iota(indices.begin(), indices.end(), 0); + test.check(std::all_of(indices.begin(), indices.end(), [&](int item) { + return map.at(item) == NonTrivial{item}; + })); + }), + + make_test("move insert range", [](auto& test) { + UnorderedMap map; + std::vector indices(small_size); + std::iota(indices.begin(), indices.end(), 0); + /*auto range = iota(0, int(small_size)) + | transform([](int item) -> std::pair { + return {item, std::to_string(item)}; + }) + | common;*/ + std::vector> storage; + std::transform(indices.begin(), indices.end(), std::back_inserter(storage), [](int idx) { + return std::pair{idx, std::to_string(idx)}; + }); + map.insert(storage.begin(), storage.end()); + test.check( + rng::all_of(storage, [](auto& pr) { return std::to_string(pr.first) == pr.second; })); + map = UnorderedMap(); + map.insert(std::move_iterator(storage.begin()), std::move_iterator(storage.end())); + test.equals(storage.size(), small_size); + test.check(rng::all_of(storage, [&](auto& p) { return test.equals(p.second, ""); })); + })}; +} + +TestGroup create_access_tests() { + return {"access", + make_test( + ".at and []", + [](auto& test) { + /*auto range = iota(0, int(small_size)) + | transform([](int item) -> std::pair { + return {item, std::to_string(item)}; + }) + | common;*/ + std::vector indices(small_size); + std::iota(indices.begin(), indices.end(), 0); + std::vector> range; + std::transform(indices.begin(), indices.end(), std::back_inserter(range), + [](int idx) { + return std::pair{idx, std::to_string(idx)}; + }); + + std::iota(indices.begin(), indices.end(), 0); + UnorderedMap map; + map.insert(range.begin(), range.end()); + for (int idx : indices) { + test.equals(std::to_string(idx), map.at(idx)); + test.equals(std::to_string(idx), map[idx]); + } + try { + map.at(-1) = "abacaba"; + test.fail(); + } catch (...) {} + map[-1] = "abacaba"; + test.equals(map.at(-1), "abacaba"); + map.at(-1) = "qwerty"; + test.equals(map[-1], "qwerty"); + }), + + make_test("[] move", + [](auto& test) { + UnorderedMap map; + /*auto range = iota(0, int(small_size)) + | transform([](int item) { + return std::to_string(item); + }) + | common;*/ + std::vector indices(small_size); + std::iota(indices.begin(), indices.end(), 0); + std::vector storage; + std::transform(indices.begin(), indices.end(), + std::back_inserter(storage), + [](int idx) { return std::to_string(idx); }); + test.check(rng::all_of(indices, [&](int i) { + return std::to_string(i) == storage[size_t(i)]; + })); + map[std::move(storage[0])] = std::move(storage[1]); + test.equals(storage[0], ""); + test.equals(storage[1], ""); + map[std::move(storage[2])] = storage[3]; + test.equals(storage[2], ""); + test.equals(storage[3], "3"); + map[storage[3]] = std::move(storage[4]); + test.equals(storage[3], "3"); + test.equals(storage[4], ""); + }), + + make_test("find", + [](auto& test) { + auto map = make_small_map(); + auto existing = map.find(1); + test.equals(existing->second, 1_tr); + auto non_existing = map.find(-1); + test.equals(non_existing, map.end()); + }), + + make_test("bucket borders check", [](auto& test) { + UnorderedMap size_t { + return static_cast(abs(element) % 10); + })> + map; + map.emplace(1, 1); + map.emplace(11, 11); + test.check(map.find(1) != map.end()); + test.check(map.find(11) != map.end()); + test.check(map.find(11) != map.find(1)); + })}; +} + +TestGroup create_misc_tests() { + return {"misc", make_test("load factor", [](auto& test) { + auto map = make_small_map(); + // auto max_val = rng::max(map | keys); + auto max_val = std::max_element(map.begin(), map.end(), [](auto& left, auto& right) { + return left.first < right.first; + })->first; + test.check(map.load_factor() > 0.0f); + auto new_load_factor = map.load_factor() / 2.0f; + map.max_load_factor(new_load_factor); + // for (auto i : iota(max_val + 1) | take(medium_size)) { + for (auto i = max_val + 1; i < max_val + 1 + int(medium_size); ++i) { + auto [_, inserted] = map.emplace(i, Trivial{i}); + test.check(inserted); + test.check(map.load_factor() > 0.0f); + test.check(map.load_factor() <= new_load_factor); + } + })}; +} +} // namespace TestsByUnrealf1 + +namespace TestsByDarkCodeForce { +struct NeitherDefaultNorCopyConstructible { + int data; + const bool moved = false; + + NeitherDefaultNorCopyConstructible() = delete; + NeitherDefaultNorCopyConstructible(const NeitherDefaultNorCopyConstructible&) = delete; + NeitherDefaultNorCopyConstructible(NeitherDefaultNorCopyConstructible&& other) + : data(other.data), + moved(true) { + other.data = 0; + }; + NeitherDefaultNorCopyConstructible(int input) + : data(input), + moved(false) {} + + auto operator<=>(const NeitherDefaultNorCopyConstructible&) const = default; +}; + + +template +struct POCMAConfigurableAllocator { + size_t allocates_counter = 0; + size_t deallocates_counter = 0; + + using propagate_on_container_move_assignment = POCMA; + using value_type = T; + + POCMAConfigurableAllocator() = default; + + template + POCMAConfigurableAllocator(const POCMAConfigurableAllocator& other) + : allocates_counter(other.allocates_counter), + deallocates_counter(other.deallocates_counter) {} + + template + POCMAConfigurableAllocator(POCMAConfigurableAllocator&& other) + : allocates_counter(other.allocates_counter), + deallocates_counter(other.deallocates_counter) { + other.allocates_counter = 0; + other.deallocates_counter = 0; + } + + using is_always_equal = AlwaysEqual; + bool operator==(const POCMAConfigurableAllocator&) const { return false; } + + T* allocate(size_t n) { + ++allocates_counter; + return std::allocator().allocate(n); + } + + void deallocate(T* pointer, size_t n) { + ++deallocates_counter; + return std::allocator().deallocate(pointer, n); + } +}; + +template + using pocma_map_type = UnorderedMap< + NeitherDefaultNorCopyConstructible, int, + decltype([](const NeitherDefaultNorCopyConstructible&) { return 1; }), + std::equal_to, + POCMAConfigurableAllocator, POCMA, + AlwaysEqual>>; + +TestGroup create_pocma_allocator_tests() { + return { + "POCMA Allocator tests", make_test("POCMA and AlwaysEqual", [&](auto&) { + pocma_map_type mtt1; + pocma_map_type mtt2; + mtt2 = std::move(mtt1); // just as with std::allocator + + pocma_map_type mtf1; + pocma_map_type mtf2; + mtf2 = std::move(mtf1); // just as with std::allocator but now they are not equal + + pocma_map_type mft1; + pocma_map_type mft2; + mft2 = std::move(mft1); // just as with std::allocator but now they are not propagated + + // pocma_map_type mff1; + // pocma_map_type mff2; + // mff2 = std::move(mff1); // and this should not compile + }) + }; +} + +}; // namespace TestsByDarkCodeForce + int main() { - std::cerr << "Starting tests" << std::endl; - SimpleTest(); - std::cerr << "SimpleTest (1 of 6) passed" << std::endl; - TestIterators(); - std::cerr << "TestIterators (2 of 6) passed" << std::endl; - TestConstIteratorDoesntAllowModification(0); - std::cerr << "TestConstIteratorDoesntAllowModification (3 of 6) passed" << std::endl; - TestNoRedundantCopies(); - std::cerr << "TestRedundantCopies (4 of 6) passed" << std::endl; - TestCustomHashAndCompare(); - std::cerr << "TestCustomHashAndCompare (5 of 6) passed" << std::endl; - TestCustomAlloc(); - std::cerr << "TestCustomAlloc (6 of 6) passed" << std::endl; - std::cout << 0; + groups_t groups{}; + + groups.push_back(TestsByMesyarik::create_basic_tests()); + groups.push_back(TestsByMesyarik::create_special_allocator_tests()); + + groups.push_back(TestsByUnrealf1::create_constructor_tests()); + groups.push_back(TestsByUnrealf1::create_modification_tests()); + groups.push_back(TestsByUnrealf1::create_access_tests()); + groups.push_back(TestsByUnrealf1::create_misc_tests()); + + groups.push_back(TestsByDarkCodeForce::create_pocma_allocator_tests()); + + bool res = true; + for (auto& group : groups) { + res &= group.run(); + } + + return res ? 0 : 1; }