160 lines
7.1 KiB
C++
160 lines
7.1 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/GuestTime.hpp"
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <barrier>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <thread>
|
|
|
|
extern "C" {
|
|
GuestTm* APS5_VABI libc_gmtime_nid_postfix(const std::int64_t*);
|
|
GuestTm* APS5_VABI gmtime_nid_postfix(const std::int64_t*);
|
|
GuestTm* APS5_VABI libc_localtime_nid_postfix(const std::int64_t*);
|
|
GuestTm* APS5_VABI localtime_nid_postfix(const std::int64_t*);
|
|
GuestTm* APS5_VABI gmtime_s_nid_postfix(const std::int64_t*, GuestTm*);
|
|
GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*);
|
|
std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*);
|
|
std::size_t APS5_VABI strftime_nid_postfix(char*, std::size_t, const char*, const GuestTm*);
|
|
void APS5_VABI tzset_nid_postfix(void);
|
|
char* APS5_VABI ctime_nid_postfix(const std::int64_t*);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
using Converter = GuestTm* (APS5_VABI *)(const std::int64_t*);
|
|
using BufferedConverter = GuestTm* (APS5_VABI *)(const std::int64_t*, GuestTm*);
|
|
|
|
static void Require(bool value, const char* message) {
|
|
if (!value) {
|
|
std::fprintf(stderr, "%s\n", message);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
static bool Equal(const GuestTm& left, const GuestTm& right) {
|
|
return left.tm_sec == right.tm_sec && left.tm_min == right.tm_min && left.tm_hour == right.tm_hour
|
|
&& left.tm_mday == right.tm_mday && left.tm_mon == right.tm_mon && left.tm_year == right.tm_year
|
|
&& left.tm_wday == right.tm_wday && left.tm_yday == right.tm_yday && left.tm_isdst == right.tm_isdst;
|
|
}
|
|
|
|
static void CheckConcurrent(Converter convert, BufferedConverter buffered, const char* name) {
|
|
constexpr std::array<std::int64_t, 8> timers{0, 86400, 946684800, 1078012800, 1609459200, 1709164800, 1893456000, 2145916799};
|
|
std::array<GuestTm, timers.size()> expected{};
|
|
for (std::size_t index = 0; index < timers.size(); ++index)
|
|
Require(buffered(&timers[index], &expected[index]) == &expected[index], "Buffered conversion failed");
|
|
std::barrier start(static_cast<std::ptrdiff_t>(timers.size()));
|
|
std::atomic<bool> matches{true};
|
|
std::array<std::thread, timers.size()> workers;
|
|
for (std::size_t index = 0; index < workers.size(); ++index) {
|
|
workers[index] = std::thread([&, index] {
|
|
start.arrive_and_wait();
|
|
for (int iteration = 0; iteration < 50000; ++iteration) {
|
|
const auto* result = convert(&timers[index]);
|
|
if (result == nullptr || !Equal(*result, expected[index])) {
|
|
matches = false;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
for (auto& worker : workers) worker.join();
|
|
Require(matches, name);
|
|
const std::int64_t invalid = std::numeric_limits<std::int64_t>::max();
|
|
Require(convert(&invalid) == nullptr, "Unrepresentable time was accepted");
|
|
}
|
|
|
|
struct UtcCase {
|
|
std::int64_t timer;
|
|
int year;
|
|
int mon;
|
|
int mday;
|
|
int hour;
|
|
int min;
|
|
int sec;
|
|
int wday;
|
|
int yday;
|
|
};
|
|
|
|
static void CheckUtc(const UtcCase& expected) {
|
|
GuestTm utc{};
|
|
std::memset(&utc, 0xAA, sizeof(utc));
|
|
Require(gmtime_s_nid_postfix(&expected.timer, &utc) == &utc, "UTC conversion failed");
|
|
Require(utc.tm_year == expected.year && utc.tm_mon == expected.mon && utc.tm_mday == expected.mday
|
|
&& utc.tm_hour == expected.hour && utc.tm_min == expected.min && utc.tm_sec == expected.sec
|
|
&& utc.tm_wday == expected.wday && utc.tm_yday == expected.yday && utc.tm_isdst == 0,
|
|
"UTC calendar fields are wrong");
|
|
Require(utc.tm_gmtoff == 0, "UTC conversion did not reset tm_gmtoff");
|
|
Require(utc.tm_zone != nullptr && std::strcmp(utc.tm_zone, "UTC") == 0, "UTC conversion did not set tm_zone");
|
|
}
|
|
|
|
int main() {
|
|
#ifdef _WIN32
|
|
_putenv_s("TZ", "UTC-2");
|
|
_tzset();
|
|
#else
|
|
setenv("TZ", "UTC-2", 1);
|
|
tzset();
|
|
#endif
|
|
constexpr std::array<UtcCase, 7> utcCases{{
|
|
{0, 70, 0, 1, 0, 0, 0, 4, 0},
|
|
{-1, 69, 11, 31, 23, 59, 59, 3, 364},
|
|
{-31536000, 69, 0, 1, 0, 0, 0, 3, 0},
|
|
{951782400, 100, 1, 29, 0, 0, 0, 2, 59},
|
|
{32535216000, 1101, 0, 1, 0, 0, 0, 4, 0},
|
|
{253402300799, 8099, 11, 31, 23, 59, 59, 5, 364},
|
|
{-62135596800, -1899, 0, 1, 0, 0, 0, 1, 0},
|
|
}};
|
|
for (const auto& utcCase : utcCases) CheckUtc(utcCase);
|
|
const std::int64_t underflow = std::numeric_limits<std::int64_t>::min();
|
|
GuestTm scratch{};
|
|
Require(gmtime_s_nid_postfix(&underflow, &scratch) == nullptr, "Unrepresentable negative time was accepted");
|
|
|
|
const std::int64_t epoch = 0;
|
|
GuestTm local{};
|
|
std::memset(&local, 0xAA, sizeof(local));
|
|
Require(localtime_s_nid_postfix(&epoch, &local) == &local && local.tm_year == 70 && local.tm_mon == 0
|
|
&& local.tm_mday == 1 && local.tm_hour == 2, "Local epoch conversion failed");
|
|
Require(local.tm_gmtoff == 7200, "Local conversion did not set tm_gmtoff");
|
|
Require(local.tm_zone != nullptr && std::strlen(local.tm_zone) > 0, "Local conversion did not set tm_zone");
|
|
|
|
GuestTm roundTrip = local;
|
|
roundTrip.tm_wday = -1;
|
|
roundTrip.tm_yday = -1;
|
|
roundTrip.tm_gmtoff = -1;
|
|
roundTrip.tm_zone = nullptr;
|
|
Require(mktime_nid_postfix(&roundTrip) == 0, "mktime did not invert localtime");
|
|
Require(roundTrip.tm_wday == 4 && roundTrip.tm_yday == 0, "mktime did not normalize the calendar fields");
|
|
Require(roundTrip.tm_gmtoff == 7200 && roundTrip.tm_zone != nullptr, "mktime did not set the zone fields");
|
|
|
|
Require(std::strcmp(ctime_nid_postfix(&epoch), "Thu Jan 1 02:00:00 1970\n") == 0, "ctime did not format local time");
|
|
const std::int64_t leap = 951782400 + 13 * 3600 + 4 * 60 + 5;
|
|
Require(std::strcmp(ctime_nid_postfix(&leap), "Tue Feb 29 15:04:05 2000\n") == 0, "ctime formatted the wrong date");
|
|
*__error_nid_postfix() = 0;
|
|
Require(std::strcmp(ctime_nid_postfix(&underflow), "??? ??? ?? ??:??:?? ????\n") == 0 && *__error_nid_postfix() == 22,
|
|
"ctime of an unrepresentable time did not report it");
|
|
|
|
GuestTm utc{};
|
|
Require(gmtime_s_nid_postfix(&epoch, &utc) == &utc, "UTC epoch conversion failed");
|
|
char formatted[64]{};
|
|
Require(strftime_nid_postfix(formatted, sizeof(formatted), "%Y-%m-%d %H:%M:%S", &utc) == 19
|
|
&& std::strcmp(formatted, "1970-01-01 00:00:00") == 0, "strftime did not read the guest tm");
|
|
|
|
CheckConcurrent(libc_gmtime_nid_postfix, gmtime_s_nid_postfix, "Concurrent libc_gmtime returned another thread's date");
|
|
CheckConcurrent(gmtime_nid_postfix, gmtime_s_nid_postfix, "Concurrent gmtime returned another thread's date");
|
|
CheckConcurrent(libc_localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent libc_localtime returned another thread's date");
|
|
CheckConcurrent(localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent localtime returned another thread's date");
|
|
#ifdef _WIN32
|
|
_putenv_s("TZ", "UTC-5");
|
|
#else
|
|
setenv("TZ", "UTC-5", 1);
|
|
#endif
|
|
tzset_nid_postfix();
|
|
GuestTm shifted{};
|
|
Require(localtime_s_nid_postfix(&epoch, &shifted) == &shifted && shifted.tm_hour == 5 && shifted.tm_gmtoff == 18000,
|
|
"tzset did not pick up the new TZ");
|
|
}
|