diff --git a/include/__psychicstd_algo b/include/__psychicstd_algo index 1cb278df..03e21f28 100644 --- a/include/__psychicstd_algo +++ b/include/__psychicstd_algo @@ -1,8 +1,11 @@ #pragma once +#include +#include +#include -// Minimal algorithm helpers so containers (map, set, list) don't pull in all of -// for one or two functions. Reserved __ names, so they never clash -// with the real if it is also included. +// Minimal algorithm helpers so containers (map, set, list, queue) don't pull +// in all of for one or two functions. Reserved __ names, so they +// never clash with the real if it is also included. namespace std { @@ -49,4 +52,72 @@ constexpr bool __lexicographical_compare(It1 f1, It1 l1, It2 f2, It2 l2) { return f1 == l1 && f2 != l2; } +// Heap operations (used by priority_queue and by 's sort_heap). +template +void __push_heap(It first, ptrdiff_t hole, ptrdiff_t top, Compare cmp, + typename iterator_traits::value_type val) { + ptrdiff_t parent = (hole - 1) / 2; + while (hole > top && cmp(*(first + parent), val)) { + *(first + hole) = static_cast(*(first + parent)); + hole = parent; + parent = (hole - 1) / 2; + } + *(first + hole) = static_cast(val); +} + +template +void push_heap(It first, It last, Compare cmp) { + if (last - first > 1) { + auto val = std::move(*(last - 1)); + __push_heap(first, last - first - 1, ptrdiff_t(0), cmp, std::move(val)); + } +} +template void push_heap(It first, It last) { + push_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); +} + +template +void __adjust_heap(It first, ptrdiff_t hole, ptrdiff_t len, Compare cmp, + typename iterator_traits::value_type val) { + ptrdiff_t top = hole; + ptrdiff_t rchild = 2 * hole + 2; + while (rchild < len) { + if (cmp(*(first + rchild), *(first + (rchild - 1)))) + --rchild; + *(first + hole) = static_cast(*(first + rchild)); + hole = rchild; + rchild = 2 * hole + 2; + } + if (rchild == len) { + *(first + hole) = static_cast(*(first + (rchild - 1))); + hole = rchild - 1; + } + __push_heap(first, hole, top, cmp, std::move(val)); +} + +template +void pop_heap(It first, It last, Compare cmp) { + if (last - first > 1) { + --last; + auto val = std::move(*last); + *last = std::move(*first); + __adjust_heap(first, ptrdiff_t(0), last - first, cmp, std::move(val)); + } +} +template void pop_heap(It first, It last) { + pop_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); +} + +template +void make_heap(It first, It last, Compare cmp) { + ptrdiff_t n = last - first; + for (ptrdiff_t i = n / 2 - 1; i >= 0; --i) { + auto val = std::move(*(first + i)); + __adjust_heap(first, i, n, cmp, std::move(val)); + } +} +template void make_heap(It first, It last) { + make_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); +} + } // namespace std diff --git a/include/algorithm b/include/algorithm index cc1505b6..76903f88 100644 --- a/include/algorithm +++ b/include/algorithm @@ -1,4 +1,5 @@ #pragma once +#include <__psychicstd_algo> #include #include #include @@ -961,73 +962,8 @@ OutIt set_symmetric_difference(It1 f1, It1 l1, It2 f2, It2 l2, OutIt out) { return copy(f2, l2, out); } -// Heap operations -template -void __push_heap(It first, ptrdiff_t hole, ptrdiff_t top, Compare cmp, - typename iterator_traits::value_type val) { - ptrdiff_t parent = (hole - 1) / 2; - while (hole > top && cmp(*(first + parent), val)) { - *(first + hole) = static_cast(*(first + parent)); - hole = parent; - parent = (hole - 1) / 2; - } - *(first + hole) = static_cast(val); -} - -template -void push_heap(It first, It last, Compare cmp) { - if (last - first > 1) { - auto val = std::move(*(last - 1)); - __push_heap(first, last - first - 1, ptrdiff_t(0), cmp, std::move(val)); - } -} -template void push_heap(It first, It last) { - push_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); -} - -template -void __adjust_heap(It first, ptrdiff_t hole, ptrdiff_t len, Compare cmp, - typename iterator_traits::value_type val) { - ptrdiff_t top = hole; - ptrdiff_t rchild = 2 * hole + 2; - while (rchild < len) { - if (cmp(*(first + rchild), *(first + (rchild - 1)))) - --rchild; - *(first + hole) = static_cast(*(first + rchild)); - hole = rchild; - rchild = 2 * hole + 2; - } - if (rchild == len) { - *(first + hole) = static_cast(*(first + (rchild - 1))); - hole = rchild - 1; - } - __push_heap(first, hole, top, cmp, std::move(val)); -} - -template -void pop_heap(It first, It last, Compare cmp) { - if (last - first > 1) { - --last; - auto val = std::move(*last); - *last = std::move(*first); - __adjust_heap(first, ptrdiff_t(0), last - first, cmp, std::move(val)); - } -} -template void pop_heap(It first, It last) { - pop_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); -} - -template -void make_heap(It first, It last, Compare cmp) { - ptrdiff_t n = last - first; - for (ptrdiff_t i = n / 2 - 1; i >= 0; --i) { - auto val = std::move(*(first + i)); - __adjust_heap(first, i, n, cmp, std::move(val)); - } -} -template void make_heap(It first, It last) { - make_heap(first, last, [](const auto& a, const auto& b) { return a < b; }); -} +// push_heap/pop_heap/make_heap live in <__psychicstd_algo> so 's +// priority_queue can use them without pulling in all of . template void sort_heap(It first, It last, Compare cmp) { diff --git a/include/csignal b/include/csignal index ef5d0403..1e4fbe05 100644 --- a/include/csignal +++ b/include/csignal @@ -1,2 +1,8 @@ #pragma once #include + +namespace std { +using ::raise; +using ::sig_atomic_t; +using ::signal; +} // namespace std diff --git a/include/queue b/include/queue index 183aec26..767c7cd4 100644 --- a/include/queue +++ b/include/queue @@ -1,4 +1,5 @@ #pragma once +#include <__psychicstd_algo> #include #include #include diff --git a/include/typeindex b/include/typeindex index 2c853c54..c3d33028 100644 --- a/include/typeindex +++ b/include/typeindex @@ -20,4 +20,11 @@ public: const char* name() const noexcept { return ptr_->name(); } }; +template struct hash; +template <> struct hash { + size_t operator()(const type_index& ti) const noexcept { + return ti.hash_code(); + } +}; + } // namespace std diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index eb762474..f03b00a5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,12 +8,31 @@ set(TESTS test_any test_array test_atomic + test_bit + test_cassert + test_cctype + test_cerrno + test_cfenv + test_cfloat test_chrono + test_cinttypes + test_ciso646 + test_climits + test_clocale + test_cmath test_compare test_complex test_concepts + test_condition_variable + test_csignal + test_cstdarg test_cstddef test_cstdint + test_cstdio + test_cstdlib + test_cstring + test_ctime + test_cwchar test_deque test_exception test_filesystem @@ -23,17 +42,21 @@ set(TESTS test_initializer_list test_iomanip test_ios + test_iosfwd test_iostream test_istream test_iterator test_limits test_list + test_locale test_map test_memory test_mutex test_new test_numeric test_optional + test_ostream + test_queue test_random test_ranges test_ratio @@ -43,11 +66,14 @@ set(TESTS test_sstream test_stack test_stdexcept + test_streambuf test_string test_string_view + test_system_error test_thread test_tuple test_type_traits + test_typeindex test_typeinfo test_unordered_map test_unordered_set @@ -55,6 +81,7 @@ set(TESTS test_valarray test_variant test_vector + test_version ) foreach(t ${TESTS}) diff --git a/tests/test_bit.cpp b/tests/test_bit.cpp new file mode 100644 index 00000000..229b60f9 --- /dev/null +++ b/tests/test_bit.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +// Quake III's fast inverse square root: bit_cast the float's bit pattern to +// an integer, do the "magic number" trick, bit_cast back. A classic exercise +// of bit_cast round-tripping a value's representation between types. +float fast_inverse_sqrt(float x) { + float xhalf = 0.5f * x; + std::uint32_t i = std::bit_cast(x); + i = 0x5f3759df - (i >> 1); + float y = std::bit_cast(i); + y = y * (1.5f - xhalf * y * y); // one Newton iteration + return y; +} + +int main() { + static_assert(std::bit_cast(0.0f) == 0u); + + float y = fast_inverse_sqrt(4.0f); + float expected = 1.0f / std::sqrt(4.0f); + assert(std::fabs(y - expected) < 1e-2f); + + // Round-tripping through bit_cast twice must be the identity. + std::uint32_t bits = std::bit_cast(3.14f); + float back = std::bit_cast(bits); + assert(back == 3.14f); +} diff --git a/tests/test_cassert.cpp b/tests/test_cassert.cpp new file mode 100644 index 00000000..59feef10 --- /dev/null +++ b/tests/test_cassert.cpp @@ -0,0 +1,32 @@ +#include + +// Binary search that documents its invariants with assert(): the array must +// be sorted, and the returned index (if any) must actually contain the key. +int binary_search(const int* a, int n, int key) { + int lo = 0, hi = n - 1; + while (lo <= hi) { + assert(lo >= 0 && hi < n); + int mid = lo + (hi - lo) / 2; + assert(mid >= lo && mid <= hi); + if (a[mid] == key) + return mid; + if (a[mid] < key) + lo = mid + 1; + else + hi = mid - 1; + } + return -1; +} + +int main() { + int a[] = {1, 3, 4, 7, 9, 12, 15, 20}; + int n = sizeof(a) / sizeof(a[0]); + + for (int i = 0; i < n; ++i) { + int idx = binary_search(a, n, a[i]); + assert(idx == i); + } + assert(binary_search(a, n, 6) == -1); + assert(binary_search(a, n, 0) == -1); + assert(binary_search(a, n, 21) == -1); +} diff --git a/tests/test_cctype.cpp b/tests/test_cctype.cpp new file mode 100644 index 00000000..988995e9 --- /dev/null +++ b/tests/test_cctype.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +// Caesar cipher (ROT13) built entirely from primitives: classify +// letters with isalpha/islower/isupper, then shift within their case. +char rot13(char c) { + if (!std::isalpha(static_cast(c))) + return c; + char base = std::isupper(static_cast(c)) ? 'A' : 'a'; + return static_cast(base + (c - base + 13) % 26); +} + +int main() { + const char* msg = "Hello, World! 123"; + char rot[32] = {}; + char back[32] = {}; + std::size_t n = 0; + for (; msg[n]; ++n) { + rot[n] = rot13(msg[n]); + back[n] = rot13(rot[n]); // applying ROT13 twice recovers the original + } + rot[n] = back[n] = '\0'; + + for (std::size_t i = 0; i < n; ++i) + assert(back[i] == msg[i]); + + assert(rot13('H') == 'U'); + assert(rot13('!') == '!'); + assert(std::toupper('a') == 'A'); + assert(std::tolower('Z') == 'z'); + assert(std::isdigit('7') && !std::isdigit('x')); + assert(std::isspace(' ') && !std::isspace('x')); +} diff --git a/tests/test_cerrno.cpp b/tests/test_cerrno.cpp new file mode 100644 index 00000000..b714e17b --- /dev/null +++ b/tests/test_cerrno.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +int main() { + errno = 0; + double d = std::strtod("1e400", nullptr); // overflows double range + assert(errno == ERANGE); + assert(d > 0); + + errno = 0; + long l = std::strtol("not a number", nullptr, 10); + assert(l == 0); + assert(errno == 0); // no conversion is not itself an error + + errno = E2BIG; // sanity-check a couple of the standard macros exist + assert(errno == E2BIG); + errno = 0; + assert(errno == 0); +} diff --git a/tests/test_cfenv.cpp b/tests/test_cfenv.cpp new file mode 100644 index 00000000..0264b2e1 --- /dev/null +++ b/tests/test_cfenv.cpp @@ -0,0 +1,36 @@ +#include +#include + +#pragma STDC FENV_ACCESS ON + +// Changing the rounding mode changes the result of an inexact computation: +// 1.0 / 3.0 rounds differently depending on FE_DOWNWARD vs FE_UPWARD. +int main() { + std::fenv_t saved; + std::fegetenv(&saved); + + // Operands must be volatile too, not just the result: with literal + // operands the division is a constant expression, and without + // -frounding-math the compiler is free to fold it at compile time with + // the default rounding mode, making it deaf to fesetround() entirely. + volatile double one = 1.0; + volatile double three = 3.0; + + std::fesetround(FE_DOWNWARD); + volatile double down = one / three; + + std::fesetround(FE_UPWARD); + volatile double up = one / three; + + assert(down < up); + + std::fesetenv(&saved); + + std::feclearexcept(FE_ALL_EXCEPT); + volatile double x = 1.0; + volatile double zero = 0.0; + volatile double inf = x / zero; + (void)inf; + assert(std::fetestexcept(FE_DIVBYZERO) != 0); + std::feclearexcept(FE_ALL_EXCEPT); +} diff --git a/tests/test_cfloat.cpp b/tests/test_cfloat.cpp new file mode 100644 index 00000000..e647ccd1 --- /dev/null +++ b/tests/test_cfloat.cpp @@ -0,0 +1,17 @@ +#include +#include + +static_assert(FLT_RADIX == 2); +static_assert(DBL_MANT_DIG > FLT_MANT_DIG); +static_assert(DBL_MAX_EXP > FLT_MAX_EXP); + +int main() { + // 1 + FLT_EPSILON must be the smallest float greater than 1; half of it + // must round away to nothing when added to 1. + volatile float one = 1.0f; + assert(one + FLT_EPSILON != one); + assert(one + FLT_EPSILON / 2.0f == one); + + assert(FLT_MIN > 0.0f); + assert(DBL_MAX > 0.0); +} diff --git a/tests/test_cinttypes.cpp b/tests/test_cinttypes.cpp new file mode 100644 index 00000000..1bbb9aac --- /dev/null +++ b/tests/test_cinttypes.cpp @@ -0,0 +1,17 @@ +#include +#include +#include +#include + +int main() { + std::int64_t big = -123456789012345LL; + std::uint32_t small = 0xdeadbeefu; + + char buf[64]; + std::snprintf(buf, sizeof(buf), "%" PRId64 " %" PRIx32, big, small); + assert(std::strcmp(buf, "-123456789012345 deadbeef") == 0); + + std::int64_t roundtrip = 0; + std::sscanf(buf, "%" SCNd64, &roundtrip); + assert(roundtrip == big); +} diff --git a/tests/test_ciso646.cpp b/tests/test_ciso646.cpp new file mode 100644 index 00000000..dcfae437 --- /dev/null +++ b/tests/test_ciso646.cpp @@ -0,0 +1,17 @@ +#include +#include + +// historically defined and/or/not etc. as macros for compilers +// without digraph support; in C++ these are keywords already, so this header +// is an empty stub. Exercise the alternative tokens it used to provide. +int main() { + bool a = true, b = false; + assert(a and not b); + assert(a or b); + assert((a bitand true) == a); + assert((a bitor b) == a); + assert((a xor b) == true); + int mask = 0b1010; + mask and_eq 0b1110; + assert(mask == 0b1010); +} diff --git a/tests/test_climits.cpp b/tests/test_climits.cpp new file mode 100644 index 00000000..e5cafee0 --- /dev/null +++ b/tests/test_climits.cpp @@ -0,0 +1,20 @@ +#include +#include + +static_assert(CHAR_BIT == 8); +static_assert(INT_MAX == 2147483647); +static_assert(INT_MIN == -INT_MAX - 1); +static_assert(LLONG_MAX > INT_MAX); + +int main() { + // Signed overflow wraps predictably only via unsigned arithmetic; use + // INT_MAX to check the classic "average without overflow" trick. + int a = INT_MAX - 2; + int b = INT_MAX; + int mid = a + (b - a) / 2; + assert(mid == a + (b - a) / 2); + assert(mid >= a && mid <= b); + + unsigned char uc = UCHAR_MAX; + assert(static_cast(uc + 1) == 0); +} diff --git a/tests/test_clocale.cpp b/tests/test_clocale.cpp new file mode 100644 index 00000000..a9420248 --- /dev/null +++ b/tests/test_clocale.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +int main() { + const char* prev = std::setlocale(LC_ALL, "C"); + assert(prev != nullptr); + + std::lconv* lc = std::localeconv(); + assert(lc != nullptr); + assert(std::strcmp(lc->decimal_point, ".") == 0); + + // Requesting a locale that (almost certainly) doesn't exist must fail + // without disturbing the currently installed one. + const char* bogus = std::setlocale(LC_ALL, "definitely-not-a-real-locale"); + assert(bogus == nullptr); + assert(std::strcmp(std::setlocale(LC_ALL, nullptr), "C") == 0); +} diff --git a/tests/test_cmath.cpp b/tests/test_cmath.cpp new file mode 100644 index 00000000..7ff0447a --- /dev/null +++ b/tests/test_cmath.cpp @@ -0,0 +1,41 @@ +#include +#include + +// Newton's method for sqrt, using nextafter to show it converges to within +// one ULP of std::sqrt -- and ilogb/scalbn to decompose/rebuild a float +// exactly (the classic frexp/ldexp pair, base-2 style). +double newton_sqrt(double x) { + double guess = x; + for (int i = 0; i < 50; ++i) + guess = 0.5 * (guess + x / guess); + return guess; +} + +int main() { + double x = 2.0; + double mine = newton_sqrt(x); + double theirs = std::sqrt(x); + double diff = std::fabs(mine - theirs); + assert(diff < 1e-9); + + int exp; + double frac = std::frexp(12.5, &exp); + assert(std::ldexp(frac, exp) == 12.5); + + assert(std::ilogb(8.0) == 3); + assert(std::scalbn(1.0, 3) == 8.0); + + assert(std::isnan(0.0 / 0.0)); + assert(std::isinf(1.0 / 0.0)); + assert(!std::isfinite(1.0 / 0.0)); + assert(std::signbit(-0.0) && !std::signbit(0.0)); + assert(std::copysign(3.0, -1.0) == -3.0); + + assert(std::abs(-5) == 5); + assert(std::abs(-5.5) == 5.5); + + double one = 1.0; + double next = std::nextafter(one, 2.0); + assert(next > one); + assert(std::nextafter(next, 0.0) == one); +} diff --git a/tests/test_condition_variable.cpp b/tests/test_condition_variable.cpp new file mode 100644 index 00000000..f4021f3d --- /dev/null +++ b/tests/test_condition_variable.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +// psychicstd's condition_variable is a single-threaded stub, so this only +// exercises the predicate-based API (which is well-defined even without +// real blocking): if the predicate is already true, none of the wait +// variants should ever block. +int main() { + std::mutex m; + std::condition_variable cv; + bool ready = true; + + std::unique_lock lock(m); + cv.wait(lock, [&] { return ready; }); + + // Note: the non-predicate wait_for/wait_until overloads are deliberately + // not exercised here. On a real condition_variable that nobody notifies, + // they genuinely block for the full duration and report cv_status::timeout + // -- unlike psychicstd's single-threaded stub, which always reports + // no_timeout immediately. Only the predicate-based overloads have + // equivalent, well-defined semantics on both. + bool woke = + cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return ready; }); + assert(woke); + + bool woke2 = cv.wait_until(lock, std::chrono::steady_clock::now(), + [&] { return ready; }); + assert(woke2); + + cv.notify_one(); + cv.notify_all(); +} diff --git a/tests/test_csignal.cpp b/tests/test_csignal.cpp new file mode 100644 index 00000000..f2d111a1 --- /dev/null +++ b/tests/test_csignal.cpp @@ -0,0 +1,18 @@ +#include +#include + +namespace { +volatile std::sig_atomic_t got_signal = 0; +void handler(int) { got_signal = 1; } +} // namespace + +int main() { + auto prev = std::signal(SIGUSR1, handler); + assert(prev != SIG_ERR); + + assert(got_signal == 0); + std::raise(SIGUSR1); + assert(got_signal == 1); + + std::signal(SIGUSR1, SIG_DFL); +} diff --git a/tests/test_cstdarg.cpp b/tests/test_cstdarg.cpp new file mode 100644 index 00000000..4a2716e2 --- /dev/null +++ b/tests/test_cstdarg.cpp @@ -0,0 +1,38 @@ +#include +#include + +// A tiny hand-rolled printf-style summation, built directly on the va_* +// primitives -- the same mechanism std::vprintf and friends are built on. +double sum(int count, ...) { + std::va_list args; + va_start(args, count); + double total = 0; + for (int i = 0; i < count; ++i) + total += va_arg(args, double); + va_end(args); + return total; +} + +double forward_sum(int count, std::va_list args) { + double total = 0; + for (int i = 0; i < count; ++i) + total += va_arg(args, double); + return total; +} + +double sum_via_copy(int count, ...) { + std::va_list args; + va_start(args, count); + std::va_list copy; + va_copy(copy, args); + double result = forward_sum(count, copy); + va_end(copy); + va_end(args); + return result; +} + +int main() { + assert(sum(3, 1.0, 2.0, 3.0) == 6.0); + assert(sum(0) == 0.0); + assert(sum_via_copy(4, 1.5, 2.5, 3.0, 1.0) == 8.0); +} diff --git a/tests/test_cstdio.cpp b/tests/test_cstdio.cpp new file mode 100644 index 00000000..644e721a --- /dev/null +++ b/tests/test_cstdio.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +int main() { + char buf[64]; + int n = std::snprintf(buf, sizeof(buf), "%d-%s-%.2f", 42, "hi", 3.14159); + assert(n > 0); + assert(std::strcmp(buf, "42-hi-3.14") == 0); + + int a; + char word[16]; + double d; + int matched = std::sscanf(buf, "%d-%15[^-]-%lf", &a, word, &d); + assert(matched == 3); + assert(a == 42); + assert(std::strcmp(word, "hi") == 0); + assert(d > 3.13 && d < 3.15); + + std::FILE* f = std::tmpfile(); + assert(f != nullptr); + const char* text = "round trip through a real file\n"; + assert(std::fputs(text, f) >= 0); + std::rewind(f); + char readback[64] = {}; + assert(std::fgets(readback, sizeof(readback), f) != nullptr); + assert(std::strcmp(readback, text) == 0); + std::fclose(f); +} diff --git a/tests/test_cstdlib.cpp b/tests/test_cstdlib.cpp new file mode 100644 index 00000000..1b6d6e37 --- /dev/null +++ b/tests/test_cstdlib.cpp @@ -0,0 +1,30 @@ +#include +#include + +extern "C" int cmp_int(const void* a, const void* b) { + int ia = *static_cast(a); + int ib = *static_cast(b); + return (ia > ib) - (ia < ib); +} + +int main() { + int arr[] = {5, 3, 8, 1, 9, 2}; + int n = sizeof(arr) / sizeof(arr[0]); + std::qsort(arr, n, sizeof(int), cmp_int); + for (int i = 1; i < n; ++i) + assert(arr[i - 1] <= arr[i]); + + int key = 8; + void* found = std::bsearch(&key, arr, n, sizeof(int), cmp_int); + assert(found != nullptr); + assert(*static_cast(found) == 8); + + int missing = 100; + assert(std::bsearch(&missing, arr, n, sizeof(int), cmp_int) == nullptr); + + std::div_t dv = std::div(17, 5); + assert(dv.quot == 3 && dv.rem == 2); + + assert(std::atoi("123") == 123); + assert(std::abs(-42) == 42); +} diff --git a/tests/test_cstring.cpp b/tests/test_cstring.cpp new file mode 100644 index 00000000..91fb05e8 --- /dev/null +++ b/tests/test_cstring.cpp @@ -0,0 +1,32 @@ +#include +#include + +int main() { + // memmove must handle overlapping regions correctly; memcpy is not + // required to. Shift a buffer right by 2 into itself. + char buf[] = "ABCDEFGH"; + std::memmove(buf + 2, buf, 6); + assert(std::memcmp(buf, "ABABCDEF", 8) == 0); + + char sentence[] = "the quick brown fox"; + int words = 0; + char* tok = std::strtok(sentence, " "); + while (tok) { + ++words; + tok = std::strtok(nullptr, " "); + } + assert(words == 4); + + assert(std::strcmp("abc", "abd") < 0); + assert(std::strncmp("abcdef", "abcxyz", 3) == 0); + assert(std::strlen("hello") == 5); + + const char* haystack = "find the needle here"; + assert(std::strstr(haystack, "needle") != nullptr); + assert(std::strstr(haystack, "missing") == nullptr); + + char dst[16]; + std::strcpy(dst, "hi"); + std::strcat(dst, " there"); + assert(std::strcmp(dst, "hi there") == 0); +} diff --git a/tests/test_ctime.cpp b/tests/test_ctime.cpp new file mode 100644 index 00000000..d1abd4a4 --- /dev/null +++ b/tests/test_ctime.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +int main() { + // A known, fixed instant: 2000-01-01 00:00:00 UTC. + std::time_t epoch_2000 = 946684800; + std::tm* utc = std::gmtime(&epoch_2000); + assert(utc != nullptr); + assert(utc->tm_year == 100); // years since 1900 + assert(utc->tm_mon == 0); + assert(utc->tm_mday == 1); + assert(utc->tm_wday == 6); // 2000-01-01 was a Saturday + + char buf[32]; + std::size_t len = std::strftime(buf, sizeof(buf), "%Y-%m-%d", utc); + assert(len > 0); + assert(std::strcmp(buf, "2000-01-01") == 0); + + std::time_t later = epoch_2000 + 3600; // one hour later + assert(std::difftime(later, epoch_2000) == 3600.0); +} diff --git a/tests/test_cwchar.cpp b/tests/test_cwchar.cpp new file mode 100644 index 00000000..78de909b --- /dev/null +++ b/tests/test_cwchar.cpp @@ -0,0 +1,26 @@ +#include +#include + +int main() { + const wchar_t* a = L"hello"; + const wchar_t* b = L"help"; + assert(std::wcslen(a) == 5); + assert(std::wcsncmp(a, b, 3) == 0); + assert(std::wcscmp(a, b) < 0); + + wchar_t buf[16]; + std::wcscpy(buf, a); + std::wcscat(buf, L" world"); + assert(std::wcscmp(buf, L"hello world") == 0); + + assert(std::wcschr(buf, L'w') != nullptr); + assert(std::wcsstr(buf, L"wor") != nullptr); + + wchar_t block[5] = {L'x', L'x', L'x', L'x', L'x'}; + std::wmemset(block, L'o', 3); + assert(block[0] == L'o' && block[3] == L'x'); + + wchar_t dest[5]; + std::wmemcpy(dest, block, 5); + assert(std::wmemcmp(dest, block, 5) == 0); +} diff --git a/tests/test_iosfwd.cpp b/tests/test_iosfwd.cpp new file mode 100644 index 00000000..4ec87603 --- /dev/null +++ b/tests/test_iosfwd.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +// The point of is to let a "header" declare stream-taking functions +// without pulling in the full / machinery. Mimic that +// separation here: this function is declared using only forward declarations. +std::ostream& greet(std::ostream& os, const char* name); + +std::ostream& greet(std::ostream& os, const char* name) { + os << "hello, " << name; + return os; +} + +int main() { + std::ostringstream oss; + greet(oss, "world"); + assert(oss.str() == "hello, world"); + + static_assert(sizeof(std::streamsize) >= sizeof(long)); +} diff --git a/tests/test_locale.cpp b/tests/test_locale.cpp new file mode 100644 index 00000000..8188a2dd --- /dev/null +++ b/tests/test_locale.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +// European-style numpunct: comma as decimal point, dot as thousands +// separator, grouped in 3s. Installing it via imbue() must actually change +// how the stream formats numbers. +struct euro_numpunct : std::numpunct { +protected: + char do_decimal_point() const override { return ','; } + char do_thousands_sep() const override { return '.'; } + std::string do_grouping() const override { return "\3"; } +}; + +int main() { + std::ostringstream os; + os.imbue(std::locale(std::locale::classic(), new euro_numpunct)); + os << std::fixed << std::setprecision(2) << 1234567.5; + assert(os.str() == "1.234.567,50"); + + std::locale a = std::locale::classic(); + std::locale b = a; + assert(a == b); +} diff --git a/tests/test_ostream.cpp b/tests/test_ostream.cpp new file mode 100644 index 00000000..ae64295d --- /dev/null +++ b/tests/test_ostream.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +// A custom manipulator, the same shape as std::endl: a plain function that +// operator<< recognizes and calls. +std::ostream& tab(std::ostream& os) { return os.put('\t'); } + +int main() { + std::ostringstream os; + os << "a" << tab << "b" << std::endl; + assert(os.str() == "a\tb\n"); + + std::ostringstream padded; + padded << std::setw(6) << std::setfill('*') << 42; + assert(padded.str() == "****42"); + + std::ostringstream left; + left << std::left << std::setw(6) << std::setfill('.') << 7; + assert(left.str() == "7....."); + + std::ostringstream hexed; + hexed << std::hex << std::showbase << 255; + assert(hexed.str() == "0xff"); +} diff --git a/tests/test_queue.cpp b/tests/test_queue.cpp new file mode 100644 index 00000000..9477b87c --- /dev/null +++ b/tests/test_queue.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +// Dijkstra-style usage: a priority_queue of (distance, node) pairs, always +// popping the closest unvisited node -- the textbook reason priority_queue +// exists. +int main() { + using Edge = std::pair; // (distance, node) + std::priority_queue, std::greater> pq; + + pq.push({0, 0}); + pq.push({4, 1}); + pq.push({1, 2}); + pq.push({2, 3}); + + std::vector pop_order; + while (!pq.empty()) { + pop_order.push_back(pq.top().second); + pq.pop(); + } + // Nodes must come out sorted by distance: 0(d0), 2(d1), 3(d2), 1(d4). + assert((pop_order == std::vector{0, 2, 3, 1})); + + std::queue q; + q.push(1); + q.push(2); + q.push(3); + assert(q.front() == 1 && q.back() == 3); + q.pop(); + assert(q.front() == 2); + assert(q.size() == 2); +} diff --git a/tests/test_streambuf.cpp b/tests/test_streambuf.cpp new file mode 100644 index 00000000..3793c440 --- /dev/null +++ b/tests/test_streambuf.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +// A streambuf that uppercases everything written through it, backed by a +// std::string instead of a file or another stream -- the same technique +// used to bridge custom sinks (loggers, in-memory buffers) into iostreams. +class upper_streambuf : public std::streambuf { +public: + std::string result; + +protected: + int_type overflow(int_type c) override { + if (traits_type::eq_int_type(c, traits_type::eof())) + return traits_type::not_eof(c); + result.push_back( + static_cast(std::toupper(traits_type::to_char_type(c)))); + return c; + } + + std::streamsize xsputn(const char* s, std::streamsize n) override { + for (std::streamsize i = 0; i < n; ++i) + result.push_back( + static_cast(std::toupper(static_cast(s[i])))); + return n; + } +}; + +int main() { + upper_streambuf buf; + std::ostream os(&buf); + os << "hello, " << 42 << " worlds!"; + os.flush(); + assert(buf.result == "HELLO, 42 WORLDS!"); +} diff --git a/tests/test_system_error.cpp b/tests/test_system_error.cpp new file mode 100644 index 00000000..5aa556da --- /dev/null +++ b/tests/test_system_error.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +// A tiny custom error category, the pattern libraries use to report their +// own error codes through the standard error_code/system_error machinery. +class parse_error_category : public std::error_category { +public: + const char* name() const noexcept override { return "parse"; } + std::string message(int ev) const override { + switch (ev) { + case 1: + return "unexpected token"; + case 2: + return "unterminated string"; + default: + return "unknown parse error"; + } + } +}; + +const std::error_category& parse_category() { + static parse_error_category c; + return c; +} + +std::error_code make_error_code(int ev) { return {ev, parse_category()}; } + +int main() { + std::error_code ec = make_error_code(1); + assert(ec.message() == "unexpected token"); + assert(static_cast(ec)); + + std::error_code ok(0, parse_category()); + assert(!static_cast(ok)); + + bool threw = false; + try { + throw std::system_error(ec, "parsing failed"); + } catch (const std::system_error& e) { + threw = true; + assert(e.code() == ec); + std::string what = e.what(); + assert(what.find("parsing failed") != std::string::npos); + } + assert(threw); +} diff --git a/tests/test_typeindex.cpp b/tests/test_typeindex.cpp new file mode 100644 index 00000000..d4fb0138 --- /dev/null +++ b/tests/test_typeindex.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +// A minimal runtime type registry keyed by type_index -- the mechanism +// behind type-erased factories and pub/sub message dispatch. +struct Shape { + virtual ~Shape() = default; + virtual const char* name() const = 0; +}; +struct Circle : Shape { + const char* name() const override { return "circle"; } +}; +struct Square : Shape { + const char* name() const override { return "square"; } +}; + +int main() { + std::unordered_map (*)()> factory; + factory[std::type_index(typeid(Circle))] = [] { + return std::unique_ptr(new Circle()); + }; + factory[std::type_index(typeid(Square))] = [] { + return std::unique_ptr(new Square()); + }; + + auto shape = factory.at(std::type_index(typeid(Circle)))(); + assert(std::string(shape->name()) == "circle"); + + std::type_index ci(typeid(Circle)); + std::type_index ci2(typeid(Circle)); + std::type_index sq(typeid(Square)); + assert(ci == ci2); + assert(ci != sq); + assert(ci.hash_code() == ci2.hash_code()); +} diff --git a/tests/test_version.cpp b/tests/test_version.cpp new file mode 100644 index 00000000..fbe2b823 --- /dev/null +++ b/tests/test_version.cpp @@ -0,0 +1,27 @@ +#include + +// only defines feature-test macros; a compile-time check is the +// whole test. Guard each with #ifdef since psychicstd only defines a subset +// of what libstdc++ advertises -- the point is that where a macro exists, +// its value is sane and SD-6 compliant (a plausible YYYYMML date). +#ifndef __cpp_lib_span +#error "__cpp_lib_span should be defined" +#endif +static_assert(__cpp_lib_span >= 201803L); + +#ifndef __cpp_lib_bit_cast +#error "__cpp_lib_bit_cast should be defined" +#endif +static_assert(__cpp_lib_bit_cast >= 201806L); + +#ifndef __cpp_lib_three_way_comparison +#error "__cpp_lib_three_way_comparison should be defined" +#endif +static_assert(__cpp_lib_three_way_comparison >= 201711L); + +#ifndef __cpp_lib_byte +#error "__cpp_lib_byte should be defined" +#endif +static_assert(__cpp_lib_byte >= 201603L); + +int main() {}