diff --git a/platforms/posix/CMakeLists.txt b/platforms/posix/CMakeLists.txt index a9f3a66526..b70002f82f 100644 --- a/platforms/posix/CMakeLists.txt +++ b/platforms/posix/CMakeLists.txt @@ -107,6 +107,7 @@ target_link_libraries(px4 PRIVATE uORB) if(WIN32) px4_posix_windows_link_libraries(px4) + px4_posix_windows_add_tests() endif() #============================================================================= diff --git a/platforms/posix/cmake/windows.cmake b/platforms/posix/cmake/windows.cmake index acf9ec0733..2f89dc8678 100644 --- a/platforms/posix/cmake/windows.cmake +++ b/platforms/posix/cmake/windows.cmake @@ -173,3 +173,13 @@ function(px4_posix_windows_link_libraries target_name) target_link_libraries(${target_name} PRIVATE winpthread) endif() endfunction() + +# Wire up the shim unit tests under platforms/posix/src/px4/windows/tests +# when CMake testing is configured. The tests are gated to Windows hosts. +function(px4_posix_windows_add_tests) + if(NOT BUILD_TESTING) + return() + endif() + add_subdirectory(${PX4_POSIX_WINDOWS_ROOT}/tests + ${CMAKE_BINARY_DIR}/platforms/posix/src/px4/windows/tests) +endfunction() diff --git a/platforms/posix/src/px4/windows/tests/CMakeLists.txt b/platforms/posix/src/px4/windows/tests/CMakeLists.txt new file mode 100644 index 0000000000..4a1a0b41f7 --- /dev/null +++ b/platforms/posix/src/px4/windows/tests/CMakeLists.txt @@ -0,0 +1,93 @@ +############################################################################ +# +# Copyright (c) 2026 PX4 Development Team. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name PX4 nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# +# Unit tests for the Windows POSIX shim layer. These targets only build +# when CMAKE_TESTING is on (BUILD_TESTING is set by include(CTest)). +# Tests are gated to Windows hosts because the shim headers are +# Windows-only by design; on non-Windows the .cpp files compile to a +# trivial sentinel test so the gtest runner still produces output. +# + +if(NOT BUILD_TESTING) + return() +endif() + +# Headers-only tests (libgen, dirent, sched, syslog, mman, time, unistd, +# getopt, fcntl). The translation unit pulls the shim headers via the +# existing windows_shim include path; no extra link libraries are +# required beyond gtest itself. +px4_add_unit_gtest(SRC test_windows_shim_headers.cpp) + +# Source-defined shim tests (errno_map, env, sysconf, mman, flock, dlfcn, +# ids, if_query, resolver, socket loopback). Pulls in the standalone +# Windows shim sources directly so the tests do not need the full PX4 +# platform/uORB stack. +set(_px4_windows_shim_root ${CMAKE_CURRENT_SOURCE_DIR}/..) + +set(_px4_windows_shim_test_srcs + ${_px4_windows_shim_root}/posix/sys/errno_map.cpp + ${_px4_windows_shim_root}/posix/sys/sysconf.cpp + ${_px4_windows_shim_root}/posix/proc/env.cpp + ${_px4_windows_shim_root}/posix/proc/ids.cpp + ${_px4_windows_shim_root}/posix/fs/flock.cpp + ${_px4_windows_shim_root}/posix/fs/mman.cpp + ${_px4_windows_shim_root}/posix/lib/dlfcn.cpp + ${_px4_windows_shim_root}/posix/net/if_query.cpp + ${_px4_windows_shim_root}/posix/net/resolver.cpp +) + +px4_add_unit_gtest( + SRC test_windows_shim_runtime.cpp + EXTRA_SRCS ${_px4_windows_shim_test_srcs} + INCLUDES + ${PX4_SOURCE_DIR}/platforms/posix/include/windows_shim + ${_px4_windows_shim_root}/include + LINKLIBS ws2_32 iphlpapi +) + +# poll() inline implementation - header-only test. +px4_add_unit_gtest( + SRC test_windows_shim_poll.cpp + INCLUDES + ${PX4_SOURCE_DIR}/platforms/posix/include/windows_shim + LINKLIBS ws2_32 +) + +# I/O helpers (pipe, fsync, dprintf, mkdir, lstat). Header-only; the +# POSIX names route to MSVC CRT primitives via inline shim wrappers. +px4_add_unit_gtest( + SRC test_windows_shim_io.cpp + INCLUDES + ${PX4_SOURCE_DIR}/platforms/posix/include/windows_shim +) diff --git a/platforms/posix/src/px4/windows/tests/test_windows_shim_headers.cpp b/platforms/posix/src/px4/windows/tests/test_windows_shim_headers.cpp new file mode 100644 index 0000000000..fc7b28a07a --- /dev/null +++ b/platforms/posix/src/px4/windows/tests/test_windows_shim_headers.cpp @@ -0,0 +1,701 @@ +/**************************************************************************** + * + * Copyright (C) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file test_windows_shim_headers.cpp + * + * Unit tests for the inline functions defined in the Windows POSIX shim + * headers under platforms/posix/include/windows_shim/. + * + * Each header that declares logic-bearing inline functions is exercised + * for happy-path, NULL/empty, boundary, and error cases. Tests gated on + * _WIN32 because the shim headers are Windows-only by design. + */ + +#include + +#ifdef _WIN32 + +#include +#include +#include +#include +#include + +/* Pull headers via the shim include path so the inline functions under + * test are the same definitions PX4 sources see. */ +#include +#include +#include +#include +#include +#include +#include + +/* basename / dirname --------------------------------------------------- */ + +TEST(WindowsShimLibgen, BasenameNull) +{ + EXPECT_STREQ(basename(nullptr), "."); +} + +TEST(WindowsShimLibgen, BasenameEmpty) +{ + char path[1] = { 0 }; + EXPECT_STREQ(basename(path), "."); +} + +TEST(WindowsShimLibgen, BasenameLeafOnly) +{ + char path[] = "file.txt"; + EXPECT_STREQ(basename(path), "file.txt"); +} + +TEST(WindowsShimLibgen, BasenamePosixSeparators) +{ + char path[] = "a/b/c/foo"; + EXPECT_STREQ(basename(path), "foo"); +} + +TEST(WindowsShimLibgen, BasenameWindowsSeparators) +{ + char path[] = "a\\b\\c\\foo.bin"; + EXPECT_STREQ(basename(path), "foo.bin"); +} + +TEST(WindowsShimLibgen, BasenameMixedSeparators) +{ + char path[] = "C:/Users\\Nuno/file"; + EXPECT_STREQ(basename(path), "file"); +} + +TEST(WindowsShimLibgen, BasenameTrailingSlashReturnsDot) +{ + char path[] = "dir/"; + EXPECT_STREQ(basename(path), "."); +} + +TEST(WindowsShimLibgen, DirnameNullOrEmpty) +{ + EXPECT_STREQ(dirname(nullptr), "."); + char empty[1] = { 0 }; + EXPECT_STREQ(dirname(empty), "."); +} + +TEST(WindowsShimLibgen, DirnameNoSeparators) +{ + char path[] = "filename"; + EXPECT_STREQ(dirname(path), "."); +} + +TEST(WindowsShimLibgen, DirnamePosixPath) +{ + char path[] = "a/b/c/foo"; + EXPECT_STREQ(dirname(path), "a/b/c"); +} + +TEST(WindowsShimLibgen, DirnameWindowsPath) +{ + char path[] = "C:\\Users\\foo\\bar"; + EXPECT_STREQ(dirname(path), "C:\\Users\\foo"); +} + +TEST(WindowsShimLibgen, DirnameRootSeparatorPreserved) +{ + char path[] = "/foo"; + /* When the only separator is at index 0, dirname should leave it + * in place to preserve the root anchor. */ + EXPECT_STREQ(dirname(path), "/"); +} + +/* sched ---------------------------------------------------------------- */ + +#if defined(_MSC_VER) && !defined(__clang__) +TEST(WindowsShimSched, PriorityRange) +{ + EXPECT_EQ(sched_get_priority_max(SCHED_OTHER), 15); + EXPECT_EQ(sched_get_priority_max(SCHED_FIFO), 15); + EXPECT_EQ(sched_get_priority_min(SCHED_OTHER), -15); + EXPECT_EQ(sched_get_priority_min(SCHED_RR), -15); +} + +TEST(WindowsShimSched, YieldReturnsZero) +{ + EXPECT_EQ(sched_yield(), 0); +} +#endif + +/* syslog --------------------------------------------------------------- */ + +TEST(WindowsShimSyslog, OpenAndCloseNoOps) +{ + openlog("ident", LOG_PID, LOG_USER); + closelog(); + SUCCEED(); +} + +TEST(WindowsShimSyslog, SetLogMaskRoundtrip) +{ + const int previous = setlogmask(LOG_UPTO(LOG_INFO)); + const int updated = setlogmask(LOG_UPTO(LOG_DEBUG)); + EXPECT_EQ(updated, LOG_UPTO(LOG_INFO)); + + /* setlogmask(0) returns the active mask without modifying it. */ + const int current = setlogmask(0); + EXPECT_EQ(current, LOG_UPTO(LOG_DEBUG)); + + /* Restore original. */ + setlogmask(previous); +} + +TEST(WindowsShimSyslog, MaskSuppressesLogging) +{ + /* Mute everything; vsyslog should bail out without writing. */ + setlogmask(LOG_MASK(LOG_EMERG)); + syslog(LOG_DEBUG, "should-not-emit-%d", 42); + setlogmask(LOG_UPTO(LOG_DEBUG)); + SUCCEED(); +} + +TEST(WindowsShimSyslog, FacilityPriorityHelpers) +{ + const int packed = LOG_MAKEPRI(LOG_USER, LOG_INFO); + EXPECT_EQ(LOG_PRI(packed), LOG_INFO); + EXPECT_EQ(LOG_FAC(packed), LOG_FAC(LOG_USER)); +} + +/* mman ----------------------------------------------------------------- */ + +TEST(WindowsShimMmanHeader, MadviseAlwaysSucceeds) +{ + char buf[64]; + EXPECT_EQ(madvise(buf, sizeof(buf), MADV_NORMAL), 0); + EXPECT_EQ(madvise(buf, sizeof(buf), MADV_RANDOM), 0); + EXPECT_EQ(madvise(nullptr, 0, MADV_DONTNEED), 0); +} + +TEST(WindowsShimMmanHeader, ProtFlagBits) +{ + EXPECT_EQ(PROT_NONE, 0); + EXPECT_NE(PROT_READ & PROT_WRITE, PROT_READ); + EXPECT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, 0x7); +} + +TEST(WindowsShimMmanHeader, MapAnonAlias) +{ + EXPECT_EQ(MAP_ANON, MAP_ANONYMOUS); +} + +TEST(WindowsShimMmanHeader, PosixMadviseAliases) +{ + EXPECT_EQ(POSIX_MADV_NORMAL, MADV_NORMAL); + EXPECT_EQ(POSIX_MADV_DONTNEED, MADV_DONTNEED); +} + +/* time helpers --------------------------------------------------------- */ + +TEST(WindowsShimTime, GmtimeRRejectsNull) +{ + struct tm out = {}; + time_t t = 0; + EXPECT_EQ(gmtime_r(nullptr, &out), nullptr); + EXPECT_EQ(gmtime_r(&t, nullptr), nullptr); +} + +TEST(WindowsShimTime, GmtimeRRoundtripEpoch) +{ + struct tm out = {}; + time_t t = 0; /* 1970-01-01 00:00:00 UTC */ + struct tm *r = gmtime_r(&t, &out); + ASSERT_NE(r, nullptr); + EXPECT_EQ(out.tm_year, 70); + EXPECT_EQ(out.tm_mon, 0); + EXPECT_EQ(out.tm_mday, 1); +} + +TEST(WindowsShimTime, LocaltimeRRejectsNull) +{ + struct tm out = {}; + time_t t = 1234567890; + EXPECT_EQ(localtime_r(nullptr, &out), nullptr); + EXPECT_EQ(localtime_r(&t, nullptr), nullptr); +} + +TEST(WindowsShimTime, LocaltimeRPopulatesYear) +{ + struct tm out = {}; + time_t t = 1234567890; /* 2009-02-13 */ + struct tm *r = localtime_r(&t, &out); + ASSERT_NE(r, nullptr); + EXPECT_GT(out.tm_year, 100); +} + +TEST(WindowsShimTime, AsctimeRRejectsNull) +{ + struct tm tm_in = {}; + char buf[26] = {}; + EXPECT_EQ(asctime_r(nullptr, buf), nullptr); + EXPECT_EQ(asctime_r(&tm_in, nullptr), nullptr); +} + +TEST(WindowsShimTime, AsctimeRPopulatesBuffer) +{ + struct tm tm_in = {}; + tm_in.tm_year = 70; + tm_in.tm_mon = 0; + tm_in.tm_mday = 1; + tm_in.tm_hour = 0; + tm_in.tm_min = 0; + tm_in.tm_sec = 0; + tm_in.tm_wday = 4; + char buf[26] = {}; + char *r = asctime_r(&tm_in, buf); + ASSERT_NE(r, nullptr); + EXPECT_GT(strlen(buf), 10u); +} + +TEST(WindowsShimTime, CtimeRRejectsNull) +{ + time_t t = 0; + char buf[26] = {}; + EXPECT_EQ(ctime_r(nullptr, buf), nullptr); + EXPECT_EQ(ctime_r(&t, nullptr), nullptr); +} + +TEST(WindowsShimTime, CtimeRRoundtrip) +{ + time_t t = 1700000000; + char buf[26] = {}; + char *r = ctime_r(&t, buf); + ASSERT_NE(r, nullptr); + EXPECT_GT(strlen(buf), 10u); +} + +TEST(WindowsShimTime, TimerMacros) +{ + struct timeval a = { 5, 200000 }; + struct timeval b = { 5, 200000 }; + EXPECT_TRUE(timerisset(&a)); + EXPECT_FALSE(timercmp(&a, &b, !=)); + struct timeval r = {}; + timeradd(&a, &b, &r); + EXPECT_EQ(r.tv_sec, 10); + EXPECT_EQ(r.tv_usec, 400000); + struct timeval s = { 6, 0 }; + struct timeval d = {}; + timersub(&s, &a, &d); + EXPECT_EQ(d.tv_sec, 0); + EXPECT_EQ(d.tv_usec, 800000); + timerclear(&a); + EXPECT_FALSE(timerisset(&a)); +} + +/* unistd usleep / sleep ----------------------------------------------- */ + +TEST(WindowsShimUnistd, UsleepZeroReturnsImmediately) +{ + auto t0 = std::chrono::steady_clock::now(); + EXPECT_EQ(usleep(0), 0); + auto dt = std::chrono::steady_clock::now() - t0; + /* Should be sub-millisecond on any reasonable host. */ + EXPECT_LT(std::chrono::duration_cast(dt).count(), 50); +} + +TEST(WindowsShimUnistd, UsleepShortRespectsLowerBound) +{ + const useconds_t target_us = 2000; /* 2 ms */ + auto t0 = std::chrono::steady_clock::now(); + ASSERT_EQ(usleep(target_us), 0); + auto dt = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + /* Allow generous early-wake margin; the shim explicitly closes the + * residual against the QPC target so we should not be early. */ + EXPECT_GE(dt, (long long)target_us - 200); +} + +TEST(WindowsShimUnistd, UsleepLongerDuration) +{ + const useconds_t target_us = 20000; /* 20 ms */ + auto t0 = std::chrono::steady_clock::now(); + ASSERT_EQ(usleep(target_us), 0); + auto dt = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + EXPECT_GE(dt, (long long)target_us - 500); + /* Be generous on the upper bound to tolerate Windows scheduler jitter. */ + EXPECT_LT(dt, (long long)target_us + 100000); +} + +TEST(WindowsShimUnistd, UsleepThreadLocalTimerReuse) +{ + /* Two consecutive sleeps in the same thread reuse the cached + * waitable timer; both must still hit the deadline. */ + for (int i = 0; i < 3; ++i) { + auto t0 = std::chrono::steady_clock::now(); + ASSERT_EQ(usleep(5000), 0); + auto dt = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + EXPECT_GE(dt, 4500); + } +} + +#if defined(_MSC_VER) && !defined(__clang__) +TEST(WindowsShimUnistd, SleepOneSecond) +{ + auto t0 = std::chrono::steady_clock::now(); + EXPECT_EQ(sleep(1u), 0u); + auto dt = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + EXPECT_GE(dt, 950); +} +#endif + +/* dirent --------------------------------------------------------------- */ + +TEST(WindowsShimDirent, OpendirNullPath) +{ + errno = 0; + EXPECT_EQ(opendir(nullptr), nullptr); + EXPECT_EQ(errno, EINVAL); +} + +TEST(WindowsShimDirent, OpendirNonexistentReturnsNull) +{ + errno = 0; + EXPECT_EQ(opendir("Z:\\definitely\\does\\not\\exist\\px4test"), nullptr); + EXPECT_EQ(errno, ENOENT); +} + +TEST(WindowsShimDirent, OpendirCwd) +{ + DIR *d = opendir("."); + ASSERT_NE(d, nullptr); + EXPECT_EQ(closedir(d), 0); +} + +TEST(WindowsShimDirent, ReaddirEnumeratesDotEntries) +{ + DIR *d = opendir("."); + ASSERT_NE(d, nullptr); + bool saw_any = false; + + while (struct dirent *e = readdir(d)) { + EXPECT_GT(strlen(e->d_name), 0u); + EXPECT_LE(strlen(e->d_name), (size_t)NAME_MAX); + saw_any = true; + } + + EXPECT_TRUE(saw_any); + EXPECT_EQ(closedir(d), 0); +} + +TEST(WindowsShimDirent, ReaddirNullStream) +{ + errno = 0; + EXPECT_EQ(readdir(nullptr), nullptr); + EXPECT_EQ(errno, EBADF); +} + +TEST(WindowsShimDirent, ClosedirNullReturnsError) +{ + errno = 0; + EXPECT_EQ(closedir(nullptr), -1); + EXPECT_EQ(errno, EBADF); +} + +TEST(WindowsShimDirent, RewinddirRestartsIteration) +{ + DIR *d = opendir("."); + ASSERT_NE(d, nullptr); + int first_pass = 0; + + while (readdir(d)) { + ++first_pass; + } + + rewinddir(d); + int second_pass = 0; + + while (readdir(d)) { + ++second_pass; + } + + EXPECT_EQ(first_pass, second_pass); + EXPECT_GT(first_pass, 0); + EXPECT_EQ(closedir(d), 0); + + /* rewinddir on null is a no-op. */ + rewinddir(nullptr); +} + +TEST(WindowsShimDirent, TelldirNull) +{ + errno = 0; + EXPECT_EQ(telldir(nullptr), -1); + EXPECT_EQ(errno, EBADF); +} + +TEST(WindowsShimDirent, TelldirAdvances) +{ + DIR *d = opendir("."); + ASSERT_NE(d, nullptr); + long start = telldir(d); + EXPECT_EQ(start, 0); + + if (readdir(d) != nullptr) { + EXPECT_GT(telldir(d), start); + } + + EXPECT_EQ(closedir(d), 0); +} + +TEST(WindowsShimDirent, SeekdirRestartsAndAdvances) +{ + DIR *d = opendir("."); + ASSERT_NE(d, nullptr); + + /* Read a couple, capture position, rewind, seekdir there. */ + if (readdir(d) && readdir(d)) { + long pos = telldir(d); + seekdir(d, 0); + EXPECT_EQ(telldir(d), 0); + seekdir(d, pos); + /* Negative offsets are ignored. */ + seekdir(d, -1); + } + + /* Null and bad inputs are silent no-ops. */ + seekdir(nullptr, 0); + EXPECT_EQ(closedir(d), 0); +} + +TEST(WindowsShimDirent, AlphasortAndVersionsort) +{ + struct dirent a = {}; + struct dirent b = {}; + strcpy(a.d_name, "alpha"); + strcpy(b.d_name, "beta"); + const struct dirent *pa = &a; + const struct dirent *pb = &b; + EXPECT_LT(alphasort(&pa, &pb), 0); + EXPECT_GT(alphasort(&pb, &pa), 0); + EXPECT_EQ(versionsort(&pa, &pb), alphasort(&pa, &pb)); +} + +TEST(WindowsShimDirent, ScandirNullList) +{ + errno = 0; + EXPECT_EQ(scandir(".", nullptr, nullptr, nullptr), -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST(WindowsShimDirent, ScandirCwdNonNegative) +{ + struct dirent **list = nullptr; + int n = scandir(".", &list, nullptr, alphasort); + ASSERT_GE(n, 0); + + for (int i = 0; i < n; ++i) { + free(list[i]); + } + + free(list); +} + +static int filter_drop_dot(const struct dirent *e) +{ + return e && e->d_name[0] != '.'; +} + +TEST(WindowsShimDirent, ScandirSelectorFilters) +{ + struct dirent **list = nullptr; + int n = scandir(".", &list, &filter_drop_dot, nullptr); + ASSERT_GE(n, 0); + + for (int i = 0; i < n; ++i) { + EXPECT_NE(list[i]->d_name[0], '.'); + free(list[i]); + } + + free(list); +} + +TEST(WindowsShimDirent, ScandirNonexistent) +{ + struct dirent **list = nullptr; + int n = scandir("Z:\\definitely\\does\\not\\exist\\px4test_scan", + &list, nullptr, nullptr); + EXPECT_EQ(n, -1); +} + +#if defined(_MSC_VER) && !defined(__clang__) +/* getopt is MSVC-only; MinGW pulls system getopt. */ +#include + +TEST(WindowsShimGetopt, SimpleShortOptions) +{ + /* getopt holds global state; reset it before each scenario. */ + optind = 1; + px4_getopt_nextchar = 1; + + char *argv[] = { + (char *)"prog", + (char *)"-a", + (char *)"-b", + (char *)"hello", + (char *)"rest", + nullptr + }; + int argc = 5; + + int c = getopt(argc, argv, "ab:"); + EXPECT_EQ(c, 'a'); + c = getopt(argc, argv, "ab:"); + EXPECT_EQ(c, 'b'); + EXPECT_STREQ(optarg, "hello"); + c = getopt(argc, argv, "ab:"); + EXPECT_EQ(c, -1); + EXPECT_LT(optind, argc); +} + +TEST(WindowsShimGetopt, UnknownOptionReturnsQuestion) +{ + optind = 1; + px4_getopt_nextchar = 1; + char *argv[] = { (char *)"prog", (char *)"-x", nullptr }; + int c = getopt(2, argv, "ab:"); + EXPECT_EQ(c, '?'); + EXPECT_EQ(optopt, 'x'); +} + +TEST(WindowsShimGetopt, MissingArgumentForRequired) +{ + optind = 1; + px4_getopt_nextchar = 1; + char *argv[] = { (char *)"prog", (char *)"-a", nullptr }; + int c = getopt(2, argv, "a:"); + /* No leading colon in optstring -> '?'. */ + EXPECT_EQ(c, '?'); +} + +TEST(WindowsShimGetopt, DashDashTerminator) +{ + optind = 1; + px4_getopt_nextchar = 1; + char *argv[] = { (char *)"prog", (char *)"--", (char *)"file", nullptr }; + int c = getopt(3, argv, "ab:"); + EXPECT_EQ(c, -1); + EXPECT_EQ(optind, 2); +} + +TEST(WindowsShimGetopt, GetoptLongMatch) +{ + optind = 1; + px4_getopt_nextchar = 1; + option longopts[] = { + { "verbose", no_argument, nullptr, 'v' }, + { "file", required_argument, nullptr, 'f' }, + { nullptr, 0, nullptr, 0 } + }; + char *argv[] = { + (char *)"prog", + (char *)"--verbose", + (char *)"--file=foo.bin", + nullptr + }; + int idx = -1; + int c = getopt_long(3, argv, "vf:", longopts, &idx); + EXPECT_EQ(c, 'v'); + EXPECT_EQ(idx, 0); + c = getopt_long(3, argv, "vf:", longopts, &idx); + EXPECT_EQ(c, 'f'); + EXPECT_STREQ(optarg, "foo.bin"); +} + +TEST(WindowsShimGetopt, GetoptLongRequiredFromNextArg) +{ + optind = 1; + px4_getopt_nextchar = 1; + option longopts[] = { + { "name", required_argument, nullptr, 'n' }, + { nullptr, 0, nullptr, 0 } + }; + char *argv[] = { + (char *)"prog", + (char *)"--name", + (char *)"alice", + nullptr + }; + int idx = -1; + int c = getopt_long(3, argv, "n:", longopts, &idx); + EXPECT_EQ(c, 'n'); + EXPECT_STREQ(optarg, "alice"); +} +#endif // MSVC + +/* Pure-header fcntl tests -- the file logic is in fcntl.h's static inline + * fcntl(); F_GETFL/F_GETFD/F_SETFD/F_SETFL paths can be exercised in + * isolation without opening Win32 handles. */ +#include + +TEST(WindowsShimFcntl, GetflReturnsZero) +{ + EXPECT_EQ(fcntl(0, F_GETFL), 0); + EXPECT_EQ(fcntl(0, F_GETFD), 0); +} + +TEST(WindowsShimFcntl, SetfdAcceptsCloexec) +{ + EXPECT_EQ(fcntl(0, F_SETFD, FD_CLOEXEC), 0); +} + +TEST(WindowsShimFcntl, SetflWithoutNonblockReturnsZero) +{ + EXPECT_EQ(fcntl(0, F_SETFL, 0), 0); +} + +TEST(WindowsShimFcntl, UnknownCommandRejected) +{ + errno = 0; + EXPECT_EQ(fcntl(0, 0xBAD), -1); + EXPECT_EQ(errno, EINVAL); +} + +#endif // _WIN32 + +/* On non-Windows builds the file compiles to a single trivial test so the + * gtest target still produces output. */ +TEST(WindowsShimHeaders, BuildSentinel) +{ + SUCCEED(); +} diff --git a/platforms/posix/src/px4/windows/tests/test_windows_shim_runtime.cpp b/platforms/posix/src/px4/windows/tests/test_windows_shim_runtime.cpp new file mode 100644 index 0000000000..a49d69b8cf --- /dev/null +++ b/platforms/posix/src/px4/windows/tests/test_windows_shim_runtime.cpp @@ -0,0 +1,619 @@ +/**************************************************************************** + * + * Copyright (C) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file test_windows_shim_runtime.cpp + * + * Unit tests for the source-defined shims in + * platforms/posix/src/px4/windows/posix/. The tests link against a thin + * library that pulls in the standalone-compilable shim sources directly + * (errno_map, env, ids, sysconf, mman, flock, dlfcn, if_query, resolver, + * sched, termios, ioctl) so they can be exercised without the full PX4 + * platform/uORB stack. + * + * Tests are gated on _WIN32; on other hosts the file compiles to a + * trivial sentinel test so the gtest runner still produces output. + */ + +#include + +#ifdef _WIN32 + +#include +#include +#include + +#define _WIN32_WINNT 0x0A00 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* errno_map prototypes -- declared in px4_windows_internal.h but the + * tests don't include that header (it pulls in the entire winsock+win32 + * stack via the shim umbrella). Forward-declare just what we need. */ +extern "C" int px4_win_error_to_errno(DWORD err); +extern "C" int px4_wsa_error_to_errno(int err); +extern "C" const char *px4_hstrerror_text(int err); + +/* errno_map ------------------------------------------------------------ */ + +TEST(WindowsShimErrnoMap, FileNotFoundMapsToENOENT) +{ + EXPECT_EQ(px4_win_error_to_errno(ERROR_FILE_NOT_FOUND), ENOENT); + EXPECT_EQ(px4_win_error_to_errno(ERROR_PATH_NOT_FOUND), ENOENT); + EXPECT_EQ(px4_win_error_to_errno(ERROR_INVALID_DRIVE), ENOENT); + EXPECT_EQ(px4_win_error_to_errno(ERROR_BAD_PATHNAME), ENOENT); +} + +TEST(WindowsShimErrnoMap, AccessDeniedMapsToEACCES) +{ + EXPECT_EQ(px4_win_error_to_errno(ERROR_ACCESS_DENIED), EACCES); + EXPECT_EQ(px4_win_error_to_errno(ERROR_SHARING_VIOLATION), EACCES); + EXPECT_EQ(px4_win_error_to_errno(ERROR_LOCK_VIOLATION), EACCES); +} + +TEST(WindowsShimErrnoMap, AlreadyExistsMapsToEEXIST) +{ + EXPECT_EQ(px4_win_error_to_errno(ERROR_ALREADY_EXISTS), EEXIST); + EXPECT_EQ(px4_win_error_to_errno(ERROR_FILE_EXISTS), EEXIST); +} + +TEST(WindowsShimErrnoMap, MiscMappings) +{ + EXPECT_EQ(px4_win_error_to_errno(ERROR_INVALID_HANDLE), EBADF); + EXPECT_EQ(px4_win_error_to_errno(ERROR_NOT_ENOUGH_MEMORY), ENOMEM); + EXPECT_EQ(px4_win_error_to_errno(ERROR_OUTOFMEMORY), ENOMEM); + EXPECT_EQ(px4_win_error_to_errno(ERROR_INVALID_PARAMETER), EINVAL); + EXPECT_EQ(px4_win_error_to_errno(ERROR_DIR_NOT_EMPTY), ENOTEMPTY); + EXPECT_EQ(px4_win_error_to_errno(ERROR_NOT_SUPPORTED), ENOTSUP); + EXPECT_EQ(px4_win_error_to_errno(ERROR_BUSY), EBUSY); + EXPECT_EQ(px4_win_error_to_errno(ERROR_DISK_FULL), ENOSPC); + EXPECT_EQ(px4_win_error_to_errno(ERROR_PROC_NOT_FOUND), ESRCH); +} + +TEST(WindowsShimErrnoMap, UnknownWin32MapsToEIO) +{ + EXPECT_EQ(px4_win_error_to_errno(0xDEADBEEF), EIO); +} + +TEST(WindowsShimErrnoMap, WsaWouldBlockMapsCorrectly) +{ + EXPECT_EQ(px4_wsa_error_to_errno(WSAEWOULDBLOCK), EWOULDBLOCK); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEINPROGRESS), EINPROGRESS); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENOTSOCK), ENOTSOCK); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEMSGSIZE), EMSGSIZE); + EXPECT_EQ(px4_wsa_error_to_errno(WSAECONNREFUSED), ECONNREFUSED); + EXPECT_EQ(px4_wsa_error_to_errno(WSAECONNRESET), ECONNRESET); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENOTCONN), ENOTCONN); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEISCONN), EISCONN); + EXPECT_EQ(px4_wsa_error_to_errno(WSAETIMEDOUT), ETIMEDOUT); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEADDRINUSE), EADDRINUSE); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEADDRNOTAVAIL), EADDRNOTAVAIL); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENETDOWN), ENETDOWN); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENETUNREACH), ENETUNREACH); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENETRESET), ENETRESET); + EXPECT_EQ(px4_wsa_error_to_errno(WSAECONNABORTED), ECONNABORTED); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEAFNOSUPPORT), EAFNOSUPPORT); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEPROTONOSUPPORT), EPROTONOSUPPORT); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEPROTOTYPE), EPROTOTYPE); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENOPROTOOPT), ENOPROTOOPT); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEOPNOTSUPP), EOPNOTSUPP); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEALREADY), EALREADY); + EXPECT_EQ(px4_wsa_error_to_errno(WSAEDESTADDRREQ), EDESTADDRREQ); + EXPECT_EQ(px4_wsa_error_to_errno(WSAENOBUFS), ENOBUFS); +} + +TEST(WindowsShimErrnoMap, UnknownWsaMapsToEIO) +{ + EXPECT_EQ(px4_wsa_error_to_errno(0x7FFFFFFF), EIO); +} + +TEST(WindowsShimErrnoMap, HstrerrorReturnsStableStrings) +{ + EXPECT_STRNE(px4_hstrerror_text(HOST_NOT_FOUND), nullptr); + EXPECT_STREQ(px4_hstrerror_text(HOST_NOT_FOUND), "Unknown host"); + EXPECT_STREQ(px4_hstrerror_text(TRY_AGAIN), "Temporary failure in name resolution"); + EXPECT_STREQ(px4_hstrerror_text(NO_RECOVERY), "Non-recoverable name server error"); + EXPECT_STREQ(px4_hstrerror_text(NO_DATA), "No address associated with name"); + EXPECT_STREQ(px4_hstrerror_text(0xBAD), "Resolver error"); +} + +/* env (setenv / unsetenv) --------------------------------------------- */ + +TEST(WindowsShimEnv, SetenvRejectsBadName) +{ + errno = 0; + EXPECT_EQ(setenv(nullptr, "v", 1), -1); + EXPECT_EQ(errno, EINVAL); + + errno = 0; + EXPECT_EQ(setenv("", "v", 1), -1); + EXPECT_EQ(errno, EINVAL); + + errno = 0; + EXPECT_EQ(setenv("BAD=NAME", "v", 1), -1); + EXPECT_EQ(errno, EINVAL); + + errno = 0; + EXPECT_EQ(setenv("OK", nullptr, 1), -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST(WindowsShimEnv, SetenvOverwriteSemantics) +{ + ASSERT_EQ(setenv("PX4_TEST_VAR", "first", 1), 0); + EXPECT_STREQ(getenv("PX4_TEST_VAR"), "first"); + + /* overwrite=0 leaves existing value intact. */ + ASSERT_EQ(setenv("PX4_TEST_VAR", "second", 0), 0); + EXPECT_STREQ(getenv("PX4_TEST_VAR"), "first"); + + /* overwrite=1 replaces. */ + ASSERT_EQ(setenv("PX4_TEST_VAR", "third", 1), 0); + EXPECT_STREQ(getenv("PX4_TEST_VAR"), "third"); + + /* unsetenv clears. */ + EXPECT_EQ(unsetenv("PX4_TEST_VAR"), 0); + const char *after = getenv("PX4_TEST_VAR"); + /* MSVCRT _putenv_s with an empty string treats the variable as + * unset; some CRT versions still return an empty string from + * getenv() for compatibility. Either is acceptable. */ + EXPECT_TRUE(after == nullptr || after[0] == '\0'); +} + +TEST(WindowsShimEnv, UnsetenvRejectsBadName) +{ + errno = 0; + EXPECT_EQ(unsetenv(nullptr), -1); + EXPECT_EQ(errno, EINVAL); + errno = 0; + EXPECT_EQ(unsetenv(""), -1); + EXPECT_EQ(errno, EINVAL); + errno = 0; + EXPECT_EQ(unsetenv("BAD=NAME"), -1); + EXPECT_EQ(errno, EINVAL); +} + +/* sysconf -------------------------------------------------------------- */ + +TEST(WindowsShimSysconf, PageSizeIsPositive) +{ + const long pg = sysconf(_SC_PAGESIZE); + EXPECT_GT(pg, 0); + /* x86/x64 page size is 4096; ARM64 is 16384. Either is reasonable. */ + EXPECT_TRUE(pg == 4096 || pg == 16384 || pg == 65536); + + EXPECT_EQ(sysconf(_SC_PAGE_SIZE), pg); +} + +TEST(WindowsShimSysconf, NprocessorsAtLeastOne) +{ + EXPECT_GE(sysconf(_SC_NPROCESSORS_ONLN), 1); + EXPECT_GE(sysconf(_SC_NPROCESSORS_CONF), 1); +} + +TEST(WindowsShimSysconf, OpenMaxNonZero) +{ + EXPECT_GT(sysconf(_SC_OPEN_MAX), 0); +} + +TEST(WindowsShimSysconf, HostnameAndLoginMaxNonZero) +{ + EXPECT_GT(sysconf(_SC_HOST_NAME_MAX), 0); + EXPECT_GT(sysconf(_SC_LOGIN_NAME_MAX), 0); +} + +TEST(WindowsShimSysconf, PhysAndAvphysPagesNonNegative) +{ + EXPECT_GE(sysconf(_SC_PHYS_PAGES), 0); + EXPECT_GE(sysconf(_SC_AVPHYS_PAGES), 0); +} + +TEST(WindowsShimSysconf, ClkTckPositive) +{ + EXPECT_GT(sysconf(_SC_CLK_TCK), 0); +} + +TEST(WindowsShimSysconf, GetpagesizeMatchesSysconf) +{ + EXPECT_EQ((long)getpagesize(), sysconf(_SC_PAGESIZE)); +} + +/* mman ----------------------------------------------------------------- */ + +TEST(WindowsShimMman, AnonymousMappingRoundtrip) +{ + const size_t len = 4096; + void *p = mmap(nullptr, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(p, MAP_FAILED); + ASSERT_NE(p, nullptr); + + /* Memory should be writable. */ + memset(p, 0xAB, len); + EXPECT_EQ(((unsigned char *)p)[0], 0xABu); + EXPECT_EQ(((unsigned char *)p)[len - 1], 0xABu); + + EXPECT_EQ(munmap(p, len), 0); +} + +TEST(WindowsShimMman, MapFixedRejected) +{ + const size_t len = 4096; + errno = 0; + void *p = mmap(reinterpret_cast(0x10000000), + len, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0); + EXPECT_EQ(p, MAP_FAILED); +} + +TEST(WindowsShimMman, MunmapNullSucceedsOrFails) +{ + /* munmap(nullptr, 0) is implementation defined. The shim must at + * least not crash. */ + int rc = munmap(nullptr, 0); + (void)rc; + SUCCEED(); +} + +TEST(WindowsShimMman, MlockMlockallNoOps) +{ + char buf[64]; + EXPECT_EQ(mlock(buf, sizeof(buf)) >= -1, true); + EXPECT_EQ(munlock(buf, sizeof(buf)) >= -1, true); + EXPECT_EQ(mlockall(MCL_CURRENT), 0); + EXPECT_EQ(munlockall(), 0); +} + +TEST(WindowsShimMman, MprotectAcceptsKnownProt) +{ + const size_t len = 4096; + void *p = mmap(nullptr, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(p, MAP_FAILED); + int rc = mprotect(p, len, PROT_READ); + (void)rc; /* implementation may return 0 or -1 depending on backing */ + EXPECT_EQ(munmap(p, len), 0); +} + +/* flock ---------------------------------------------------------------- */ + +TEST(WindowsShimFlock, ExclusiveOnTempFile) +{ + char path[MAX_PATH]; + GetTempPathA(sizeof(path), path); + strcat_s(path, sizeof(path), "px4_flock_test.lock"); + + int fd = _open(path, _O_CREAT | _O_RDWR, _S_IREAD | _S_IWRITE); + ASSERT_GE(fd, 0); + + EXPECT_EQ(flock(fd, LOCK_EX | LOCK_NB), 0); + EXPECT_EQ(flock(fd, LOCK_UN), 0); + + _close(fd); + DeleteFileA(path); +} + +TEST(WindowsShimFlock, BadFdReturnsError) +{ + errno = 0; + EXPECT_EQ(flock(-1, LOCK_EX), -1); +} + +/* dlfcn ---------------------------------------------------------------- */ + +TEST(WindowsShimDlfcn, OpenNullReturnsHandle) +{ + /* Some implementations return a sentinel pseudo-handle for the + * current process; others return NULL. Both are acceptable as + * long as dlerror() is callable and dlclose() handles the result. */ + void *h = dlopen(nullptr, 0); + const char *err = dlerror(); + (void)err; + + if (h) { + EXPECT_EQ(dlclose(h), 0); + } +} + +TEST(WindowsShimDlfcn, OpenNonexistentSetsDlerror) +{ + void *h = dlopen("definitely-not-a-real-library-xxx.dll", 0); + EXPECT_EQ(h, nullptr); + const char *err = dlerror(); + EXPECT_NE(err, nullptr); +} + +TEST(WindowsShimDlfcn, SymInModule) +{ + /* kernel32 is always loaded; resolve a known export. */ + void *h = dlopen("kernel32.dll", 0); + + if (h) { + void *sym = dlsym(h, "GetCurrentProcessId"); + EXPECT_NE(sym, nullptr); + dlclose(h); + } +} + +TEST(WindowsShimDlfcn, DladdrAcceptsCallSite) +{ + Dl_info info = {}; + int rc = dladdr((void *)&dladdr, &info); + /* Some shim builds may not implement dladdr; ensure return + * value matches POSIX shape (non-zero on success / 0 on error). */ + (void)rc; + SUCCEED(); +} + +TEST(WindowsShimDlfcn, DlerrorClearsAfterRead) +{ + (void)dlopen("definitely-not-a-real-library-yyy.dll", 0); + const char *first = dlerror(); + const char *second = dlerror(); + (void)first; + /* Second call should return null per POSIX. */ + EXPECT_EQ(second, nullptr); +} + +/* ids: getppid / setsid / getsid -------------------------------------- */ + +TEST(WindowsShimIds, GetppidNeverFails) +{ + pid_t pp = getppid(); + (void)pp; + SUCCEED(); +} + +TEST(WindowsShimIds, SetsidReturnsAnId) +{ + pid_t s = setsid(); + EXPECT_NE(s, (pid_t) - 1); +} + +TEST(WindowsShimIds, GetsidSelf) +{ + pid_t s = getsid(0); + EXPECT_NE(s, (pid_t) - 1); +} + +TEST(WindowsShimIds, GetsidNonexistent) +{ + errno = 0; + pid_t s = getsid(0x7FFFFFFE); + EXPECT_EQ(s, (pid_t) - 1); + EXPECT_EQ(errno, ESRCH); +} + +/* if_query ------------------------------------------------------------- */ + +TEST(WindowsShimIfQuery, NameindexEnumerates) +{ + struct if_nameindex *list = if_nameindex(); + + if (list) { + bool found_name = false; + + for (struct if_nameindex *p = list; p->if_name; ++p) { + EXPECT_NE(p->if_name, nullptr); + found_name = found_name || (p->if_name[0] != '\0'); + } + + (void)found_name; + if_freenameindex(list); + SUCCEED(); + + } else { + /* Acceptable when running without network privileges. */ + SUCCEED(); + } +} + +TEST(WindowsShimIfQuery, FreeNullSafe) +{ + if_freenameindex(nullptr); + SUCCEED(); +} + +/* resolver: inet_aton, inet_makeaddr, inet_lnaof, inet_netof, gethostent */ + +TEST(WindowsShimResolver, InetAtonValid) +{ + struct in_addr a = {}; + EXPECT_NE(inet_aton("127.0.0.1", &a), 0); +} + +TEST(WindowsShimResolver, InetAtonInvalid) +{ + struct in_addr a = {}; + EXPECT_EQ(inet_aton("not.an.ip.addr.zzzz", &a), 0); +} + +TEST(WindowsShimResolver, InetNtoaR) +{ + struct in_addr a; + a.s_addr = htonl(0x7F000001); + char buf[32] = {}; + const char *r = inet_ntoa_r(a, buf, sizeof(buf)); + EXPECT_NE(r, nullptr); + EXPECT_STREQ(buf, "127.0.0.1"); +} + +TEST(WindowsShimResolver, MakeAddrSplit) +{ + struct in_addr a = inet_makeaddr(0x7F, 0x000001); + (void)a; + SUCCEED(); +} + +TEST(WindowsShimResolver, NetofLnaof) +{ + struct in_addr a; + a.s_addr = htonl(0x7F000001); + in_addr_t net = inet_netof(a); + in_addr_t host = inet_lnaof(a); + EXPECT_NE(net + host, 0u); +} + +TEST(WindowsShimResolver, HostentIterators) +{ + sethostent(0); + struct hostent *e = gethostent(); + (void)e; + endhostent(); + SUCCEED(); +} + +TEST(WindowsShimResolver, NetentIterators) +{ + setnetent(0); + struct netent *n = getnetent(); + (void)n; + endnetent(); + struct netent *byname = getnetbyname("loopback"); + (void)byname; + struct netent *byaddr = getnetbyaddr(0x7F000000, AF_INET); + (void)byaddr; + SUCCEED(); +} + +TEST(WindowsShimResolver, ProtoentIterators) +{ + setprotoent(0); + struct protoent *p = getprotoent(); + (void)p; + endprotoent(); + SUCCEED(); +} + +TEST(WindowsShimResolver, ServentIterators) +{ + setservent(0); + struct servent *s = getservent(); + (void)s; + endservent(); + SUCCEED(); +} + +TEST(WindowsShimResolver, HstrerrorTextNonNull) +{ + EXPECT_NE(hstrerror(HOST_NOT_FOUND), nullptr); + EXPECT_NE(hstrerror(0), nullptr); +} + +/* sockets - minimal loopback round trip --------------------------------- */ + +namespace +{ +class WinsockBootstrap +{ +public: + WinsockBootstrap() + { + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + } + + ~WinsockBootstrap() + { + WSACleanup(); + } +}; +} + +TEST(WindowsShimSocket, UdpLoopbackSendRecv) +{ + WinsockBootstrap _ws; + + SOCKET srv = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_NE(srv, INVALID_SOCKET); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; /* any */ + ASSERT_EQ(bind(srv, (struct sockaddr *)&addr, sizeof(addr)), 0); + + int alen = sizeof(addr); + ASSERT_EQ(getsockname(srv, (struct sockaddr *)&addr, &alen), 0); + + SOCKET cli = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_NE(cli, INVALID_SOCKET); + + const char msg[] = "ping"; + ASSERT_EQ(sendto(cli, msg, (int)sizeof(msg), 0, + (struct sockaddr *)&addr, sizeof(addr)), + (int)sizeof(msg)); + + char buf[16] = {}; + struct sockaddr_in from; + int from_len = sizeof(from); + int n = recvfrom(srv, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &from_len); + EXPECT_EQ(n, (int)sizeof(msg)); + EXPECT_STREQ(buf, "ping"); + + closesocket(srv); + closesocket(cli); +} + +TEST(WindowsShimSocket, SetsockoptReuseaddr) +{ + WinsockBootstrap _ws; + SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_NE(s, INVALID_SOCKET); + int yes = 1; + int rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const char *)&yes, sizeof(yes)); + EXPECT_EQ(rc, 0); + closesocket(s); +} + +#endif // _WIN32 + +/* Sentinel test so the binary always has at least one passing case. */ +TEST(WindowsShimRuntime, BuildSentinel) +{ + SUCCEED(); +}