kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

windows-system-dlls-smoke.sh (14762B)


      1 #!/usr/bin/env bash
      2 # test/coff/windows-system-dlls-smoke.sh — Type-K (mode P) Windows system-DLL
      3 # coverage smoke, on the shared scripted-shell kit (test/lib/kit_sh_kit.sh).
      4 #
      5 # Companion to windows-ucrt-hosted-smoke.sh: that proves the UCRT console + GUI
      6 # round-trip for one program per surface; this broadens coverage across the
      7 # large system DLLs an application links against:
      8 #
      9 #   user32 + gdi32 (GUI window + drawing)
     10 #   advapi32       (registry)
     11 #   ws2_32         (Winsock lifecycle)
     12 #   ole32          (COM init)
     13 #   shell32        (CommandLineToArgvW)
     14 #   comctl32       (InitCommonControls)
     15 #   libucrt.a      (mixed short-import + long-form members)
     16 #
     17 # Each program is built with `kit cc` for both x86_64-windows and
     18 # aarch64-windows; the link-level check inspects the PE import directory via
     19 # `kit objdump -p` (run_ok + contains). The Wine runtime check is run
     20 # conditionally and self-skips when the matching podman/Wine container is
     21 # absent.
     22 #
     23 # Serial by design (mode P): all programs share one $work sandbox.
     24 #
     25 # Self-skip: prints SKIP and exits 0 when no llvm-mingw UCRT sysroot is found.
     26 set -u
     27 
     28 ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}
     29 KIT=${KIT:-"$ROOT/build/kit"}
     30 SDK=${KIT_SYSROOT:-}
     31 
     32 KIT_KIT_DIR="$ROOT/test/lib"
     33 . "$ROOT/test/lib/kit_sh_kit.sh"
     34 kit_report_init
     35 
     36 LABEL_SUITE=windows-system-dlls-smoke
     37 
     38 find_sdk() {
     39   local arch=$1
     40   local d
     41   for d in \
     42     "${XDG_CACHE_HOME:-$HOME/.cache}"/kit/sysroots/windows/*/ucrt/"$arch"-w64-mingw32 \
     43     "$ROOT"/build/llvm-mingw/*/ucrt/"$arch"-w64-mingw32 \
     44     /tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \
     45     /tmp/llvm-mingw*/"$arch"-w64-mingw32 \
     46     /private/tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \
     47     /private/tmp/llvm-mingw*/"$arch"-w64-mingw32; do
     48     if [ -d "$d/lib" ] && [ -r "$d/include/windows.h" ]; then
     49       printf '%s\n' "$d"
     50       return 0
     51     fi
     52   done
     53   return 1
     54 }
     55 
     56 sdk_for_arch() {
     57   local arch=$1
     58   local base
     59   if [ -n "$SDK" ]; then
     60     if [ "$(basename "$SDK")" = "$arch-w64-mingw32" ]; then
     61       printf '%s\n' "$SDK"
     62       return 0
     63     fi
     64     base=$(dirname "$SDK")
     65     if [ -d "$base/$arch-w64-mingw32/lib" ] &&
     66        [ -r "$base/$arch-w64-mingw32/include/windows.h" ]; then
     67       printf '%s\n' "$base/$arch-w64-mingw32"
     68       return 0
     69     fi
     70   fi
     71   find_sdk "$arch"
     72 }
     73 
     74 # kit presence: was a hard FAIL+exit 1 in the original. Preserve that verdict.
     75 if [ ! -x "$KIT" ]; then
     76   kit_fail "$LABEL_SUITE/kit-present" "kit binary not found: $KIT"
     77   kit_summary "$LABEL_SUITE"
     78   kit_exit
     79 fi
     80 
     81 TMP=${TMPDIR:-/tmp}
     82 work=$(mktemp -d "$TMP/kit-windows-system-dlls-smoke.XXXXXX")
     83 WORK_REAL=$(cd "$work" && pwd -P)
     84 trap 'rm -rf "$work"' EXIT
     85 
     86 GUI_C=$work/gui_hello_window.c
     87 GDI_C=$work/gdi_drawing.c
     88 REG_C=$work/advapi32_registry.c
     89 WS_C=$work/ws2_32_socket.c
     90 OLE_C=$work/ole32_coinit.c
     91 SHELL_C=$work/shell32_argv.c
     92 COMCTL_C=$work/comctl32_init.c
     93 MIXED_C=$work/mixed_ucrt.c
     94 
     95 # A hidden window + minimal message-pump program. Wine in a headless
     96 # container may legitimately refuse to create a real window; the
     97 # program tolerates that and returns 0 so the link-level imports check
     98 # is what gates the test.
     99 cat >"$GUI_C" <<'SRC'
    100 #define UNICODE
    101 #define _UNICODE
    102 #include <windows.h>
    103 
    104 static LRESULT CALLBACK wp(HWND h, UINT m, WPARAM w, LPARAM l) {
    105   if (m == WM_DESTROY) PostQuitMessage(0);
    106   return DefWindowProcW(h, m, w, l);
    107 }
    108 
    109 int WINAPI WinMain(HINSTANCE i, HINSTANCE p, LPSTR c, int s) {
    110   (void)p; (void)c; (void)s;
    111   WNDCLASSEXW wc;
    112   ZeroMemory(&wc, sizeof(wc));
    113   wc.cbSize = sizeof(wc);
    114   wc.lpfnWndProc = wp;
    115   wc.hInstance = i;
    116   wc.lpszClassName = L"kit_hello";
    117   wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
    118   (void)RegisterClassExW(&wc);
    119   HWND h = CreateWindowExW(0, L"kit_hello", L"kit", WS_OVERLAPPEDWINDOW,
    120                            0, 0, 16, 16, NULL, NULL, i, NULL);
    121   if (h) {
    122     PostMessageW(h, WM_QUIT, 0, 0);
    123     MSG msg;
    124     while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
    125       if (msg.message == WM_QUIT) break;
    126       TranslateMessage(&msg);
    127       DispatchMessageW(&msg);
    128     }
    129     DestroyWindow(h);
    130   }
    131   return 0;
    132 }
    133 SRC
    134 
    135 # gdi32 surface: create a memory DC, select a stock font and brush,
    136 # release. Stock objects do not require an active display, so this
    137 # runs cleanly under Wine in headless containers.
    138 cat >"$GDI_C" <<'SRC'
    139 #include <windows.h>
    140 
    141 int main(void) {
    142   HDC screen = GetDC(NULL);
    143   HDC mem = CreateCompatibleDC(screen);
    144   HGDIOBJ old_font = SelectObject(mem, GetStockObject(SYSTEM_FONT));
    145   HGDIOBJ old_brush = SelectObject(mem, GetStockObject(WHITE_BRUSH));
    146   TEXTMETRICW tm;
    147   GetTextMetricsW(mem, &tm);
    148   SelectObject(mem, old_font);
    149   SelectObject(mem, old_brush);
    150   DeleteDC(mem);
    151   ReleaseDC(NULL, screen);
    152   return tm.tmHeight > 0 ? 0 : 1;
    153 }
    154 SRC
    155 
    156 # advapi32 surface: open a well-known read-only registry key and close
    157 # it. HKEY_CURRENT_USER\Environment exists by default under Wine.
    158 cat >"$REG_C" <<'SRC'
    159 #include <windows.h>
    160 
    161 int main(void) {
    162   HKEY h = NULL;
    163   LONG rc = RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_READ, &h);
    164   if (rc == ERROR_SUCCESS) {
    165     DWORD subkeys = 0, values = 0;
    166     RegQueryInfoKeyW(h, NULL, NULL, NULL, &subkeys, NULL, NULL, &values,
    167                      NULL, NULL, NULL, NULL);
    168     RegCloseKey(h);
    169     return 0;
    170   }
    171   /* Some Wine configurations may not pre-create the Environment key.
    172    * The link-level test (imports satisfied) is what we care about. */
    173   return rc == ERROR_FILE_NOT_FOUND ? 0 : 2;
    174 }
    175 SRC
    176 
    177 # ws2_32 surface: full WSAStartup/socket/closesocket/WSACleanup
    178 # lifecycle with no network traffic.
    179 cat >"$WS_C" <<'SRC'
    180 #include <winsock2.h>
    181 #include <windows.h>
    182 
    183 int main(void) {
    184   WSADATA wsa;
    185   if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;
    186   SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
    187   if (s == INVALID_SOCKET) { WSACleanup(); return 2; }
    188   closesocket(s);
    189   WSACleanup();
    190   return 0;
    191 }
    192 SRC
    193 
    194 # ole32 surface: COM apartment init/teardown.
    195 cat >"$OLE_C" <<'SRC'
    196 #include <windows.h>
    197 #include <objbase.h>
    198 
    199 int main(void) {
    200   HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    201   if (FAILED(hr)) return 1;
    202   CoUninitialize();
    203   return 0;
    204 }
    205 SRC
    206 
    207 # shell32 surface: CommandLineToArgvW round-trip.
    208 cat >"$SHELL_C" <<'SRC'
    209 #include <windows.h>
    210 #include <shellapi.h>
    211 
    212 int main(void) {
    213   int argc = 0;
    214   LPWSTR cmd = GetCommandLineW();
    215   LPWSTR* argv = CommandLineToArgvW(cmd, &argc);
    216   int ok = (argv != NULL && argc >= 1);
    217   if (argv) LocalFree(argv);
    218   return ok ? 0 : 1;
    219 }
    220 SRC
    221 
    222 # comctl32 surface: legacy InitCommonControls. Pulls in comctl32.dll
    223 # imports without needing a real display.
    224 cat >"$COMCTL_C" <<'SRC'
    225 #include <windows.h>
    226 #include <commctrl.h>
    227 
    228 int main(void) {
    229   INITCOMMONCONTROLSEX icc;
    230   ZeroMemory(&icc, sizeof(icc));
    231   icc.dwSize = sizeof(icc);
    232   icc.dwICC = ICC_STANDARD_CLASSES;
    233   /* Both spellings exist in libcomctl32.a; we use the simpler one. */
    234   InitCommonControls();
    235   return InitCommonControlsEx(&icc) ? 0 : 0;
    236 }
    237 SRC
    238 
    239 # Mixed libucrt.a members: pulls in both short-import members
    240 # (api-ms-win-crt-stdio for puts/fflush) and a long-form COFF helper
    241 # (fabsf lives in lib64_libmingwex_a-*.o as a real .o member).
    242 cat >"$MIXED_C" <<'SRC'
    243 #include <math.h>
    244 #include <stdio.h>
    245 #include <windows.h>
    246 
    247 int main(void) {
    248   float x = fabsf(-1.5f);
    249   HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
    250   DWORD wrote = 0;
    251   WriteFile(out, "mixed-ok\n", 9, &wrote, 0);
    252   fflush(stdout);
    253   return x == 1.5f ? 0 : 1;
    254 }
    255 SRC
    256 
    257 # ---- assert helpers (mode-P verbs, $work-confined) -------------------------
    258 
    259 no_legacy_crt_imports() {
    260   local name=$1 dump=$2
    261   if grep -Eiq 'DLL Name: (msvcrt|ucrt)\.dll' "$dump"; then
    262     grep -Ei 'DLL Name: (msvcrt|ucrt)\.dll' "$dump" > "$work/$name.diag"
    263     not_ok "$name" "$work/$name.diag"
    264   else
    265     ok "$name"
    266   fi
    267 }
    268 
    269 run_wine_if_available() {
    270   local label=$1 image=$2 pod_arch=$3 exe=$4
    271   shift 4
    272   case "$pod_arch" in
    273     amd64)
    274       if [ -n "${KIT_WINDOWS_VM_X64:-${KIT_WINDOWS_VM_AMD64:-}}" ]; then
    275         if "$ROOT/scripts/windows_vm.sh" run x64 "$exe" "$@" \
    276             > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then
    277           ok "$label-vm"
    278         else
    279           not_ok "$label-vm" "$work/$label-vm.err"
    280         fi
    281         return 0
    282       fi
    283       ;;
    284     arm64)
    285       if [ -n "${KIT_WINDOWS_VM_AARCH64:-${KIT_WINDOWS_VM_ARM64:-}}" ]; then
    286         if "$ROOT/scripts/windows_vm.sh" run aarch64 "$exe" "$@" \
    287             > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then
    288           ok "$label-vm"
    289         else
    290           not_ok "$label-vm" "$work/$label-vm.err"
    291         fi
    292         return 0
    293       fi
    294       ;;
    295   esac
    296   if ! command -v podman >/dev/null 2>&1; then
    297     skip_test "$label-wine" "podman unavailable"
    298     return 0
    299   fi
    300   if ! podman image exists "$image" >/dev/null 2>&1; then
    301     skip_test "$label-wine" "$image unavailable"
    302     return 0
    303   fi
    304   if podman run --rm --arch "$pod_arch" -v "$WORK_REAL:/probe:ro" "$image" \
    305       bash -lc "
    306         export WINEDEBUG=-all WINEPREFIX=/tmp/wineprefix
    307         timeout 120s /usr/lib/wine/wine64 /probe/$(basename "$exe") $*
    308         rc=\$?
    309         echo \"$label exit=\$rc\"
    310         test \"\$rc\" -eq 0
    311       " > "$work/$label-wine.out" 2> "$work/$label-wine.err"; then
    312     ok "$label-wine"
    313   else
    314     not_ok "$label-wine" "$work/$label-wine.err"
    315   fi
    316 }
    317 
    318 # build_and_check <label> <c-source> <exe> <dump> <link-mode> <libs>
    319 #   <expected-dll-1> [<expected-dll-2> ...] -- [<expected-sym-1> ...]
    320 #
    321 # link-mode is "console" or "windows" (drives -mconsole vs -mwindows). libs is
    322 # a space-separated list of `-l<name>` archives to add (e.g. "gdi32 ws2_32")
    323 # beyond the driver-auto-linked set (kernel32/user32/advapi32/shell32/ucrt/
    324 # mingwex/mingw32/moldname). Each expected DLL/symbol is one mode-P assert.
    325 build_and_check() {
    326   local label=$1 csrc=$2 exe=$3 dump=$4 mode=$5 libs=$6
    327   shift 6
    328 
    329   local dlls=() syms=() in_syms=0
    330   while [ $# -gt 0 ]; do
    331     if [ "$1" = "--" ]; then in_syms=1; shift; continue; fi
    332     if [ "$in_syms" -eq 0 ]; then dlls+=("$1"); else syms+=("$1"); fi
    333     shift
    334   done
    335 
    336   local mode_flag=-mconsole
    337   if [ "$mode" = "windows" ]; then mode_flag=-mwindows; fi
    338 
    339   local extra_lflags=() lib
    340   for lib in $libs; do extra_lflags+=("-l$lib"); done
    341 
    342   local bn; bn=$(printf '%s' "$label" | tr ' ' '-')
    343 
    344   run_ok "$bn-build" "$KIT" cc -target "$TARGET" --sysroot "$ARCH_SDK" \
    345     "$mode_flag" "$csrc" "${extra_lflags[@]}" -o "$exe"
    346   if [ ! -f "$exe" ]; then return 1; fi
    347   run_ok "$bn-objdump" "$KIT" objdump -p "$exe"
    348   if [ ! -s "$work/$bn-objdump.out" ]; then return 1; fi
    349   cp "$work/$bn-objdump.out" "$dump"
    350 
    351   no_legacy_crt_imports "$bn-no-legacy-crt" "$dump"
    352 
    353   local d
    354   for d in "${dlls[@]}"; do
    355     contains "$bn-dll-$d" "$dump" "DLL Name: $d"
    356   done
    357 
    358   local s
    359   for s in "${syms[@]}"; do
    360     contains "$bn-sym-$s" "$dump" "Name:     $s"
    361   done
    362 
    363   if [ "$mode" = "windows" ]; then
    364     contains "$bn-subsystem-gui" "$dump" "Subsystem:           2  (WINDOWS_GUI)"
    365   fi
    366 }
    367 
    368 ran=0
    369 for arch in x86_64 aarch64; do
    370   case "$arch" in
    371     x86_64)
    372       TARGET=x86_64-windows
    373       LABEL=x64
    374       IMAGE=localhost/kit-wine-amd64
    375       POD_ARCH=amd64
    376       ;;
    377     aarch64)
    378       TARGET=aarch64-windows
    379       LABEL=aarch64
    380       IMAGE=localhost/kit-wine-arm64
    381       POD_ARCH=arm64
    382       ;;
    383   esac
    384 
    385   if ! ARCH_SDK=$(sdk_for_arch "$arch"); then
    386     skip_test "$LABEL_SUITE/$LABEL-sysroot" "no $arch llvm-mingw UCRT sysroot"
    387     continue
    388   fi
    389   # Discovered-but-invalid sysroot was a SKIP (exit 0 via continue) originally.
    390   if [ ! -r "$ARCH_SDK/include/windows.h" ] ||
    391      [ ! -r "$ARCH_SDK/lib/libucrt.a" ]; then
    392     skip_test "$LABEL_SUITE/$LABEL-sysroot" "invalid UCRT llvm-mingw sysroot: $ARCH_SDK"
    393     continue
    394   fi
    395 
    396   ran=1
    397 
    398   # ---- GUI hello window (user32 + gdi32 + kernel32) ----
    399   GUI_EXE=$work/gui_hello_window-$LABEL.exe
    400   GUI_DUMP=$work/gui_hello_window-$LABEL.dump
    401   build_and_check "$LABEL gui_hello_window" "$GUI_C" "$GUI_EXE" "$GUI_DUMP" \
    402     windows "" USER32.dll KERNEL32.dll -- RegisterClassExW CreateWindowExW \
    403     DefWindowProcW PeekMessageW DispatchMessageW
    404   run_wine_if_available "$LABEL gui_hello_window" "$IMAGE" "$POD_ARCH" "$GUI_EXE"
    405 
    406   # ---- gdi32 surface ----
    407   GDI_EXE=$work/gdi_drawing-$LABEL.exe
    408   GDI_DUMP=$work/gdi_drawing-$LABEL.dump
    409   build_and_check "$LABEL gdi_drawing" "$GDI_C" "$GDI_EXE" "$GDI_DUMP" \
    410     console gdi32 GDI32.dll USER32.dll -- CreateCompatibleDC GetStockObject \
    411     SelectObject DeleteDC
    412   run_wine_if_available "$LABEL gdi_drawing" "$IMAGE" "$POD_ARCH" "$GDI_EXE"
    413 
    414   # ---- advapi32 surface ----
    415   REG_EXE=$work/advapi32_registry-$LABEL.exe
    416   REG_DUMP=$work/advapi32_registry-$LABEL.dump
    417   build_and_check "$LABEL advapi32_registry" "$REG_C" "$REG_EXE" "$REG_DUMP" \
    418     console "" ADVAPI32.dll KERNEL32.dll -- RegOpenKeyExW RegCloseKey \
    419     RegQueryInfoKeyW
    420   run_wine_if_available "$LABEL advapi32_registry" "$IMAGE" "$POD_ARCH" "$REG_EXE"
    421 
    422   # ---- ws2_32 surface ----
    423   WS_EXE=$work/ws2_32_socket-$LABEL.exe
    424   WS_DUMP=$work/ws2_32_socket-$LABEL.dump
    425   build_and_check "$LABEL ws2_32_socket" "$WS_C" "$WS_EXE" "$WS_DUMP" \
    426     console ws2_32 WS2_32.dll KERNEL32.dll -- WSAStartup WSACleanup socket \
    427     closesocket
    428   run_wine_if_available "$LABEL ws2_32_socket" "$IMAGE" "$POD_ARCH" "$WS_EXE"
    429 
    430   # ---- ole32 surface ----
    431   OLE_EXE=$work/ole32_coinit-$LABEL.exe
    432   OLE_DUMP=$work/ole32_coinit-$LABEL.dump
    433   build_and_check "$LABEL ole32_coinit" "$OLE_C" "$OLE_EXE" "$OLE_DUMP" \
    434     console ole32 ole32.dll KERNEL32.dll -- CoInitializeEx CoUninitialize
    435   run_wine_if_available "$LABEL ole32_coinit" "$IMAGE" "$POD_ARCH" "$OLE_EXE"
    436 
    437   # ---- shell32 surface ----
    438   SHELL_EXE=$work/shell32_argv-$LABEL.exe
    439   SHELL_DUMP=$work/shell32_argv-$LABEL.dump
    440   build_and_check "$LABEL shell32_argv" "$SHELL_C" "$SHELL_EXE" "$SHELL_DUMP" \
    441     console "" SHELL32.dll KERNEL32.dll -- CommandLineToArgvW
    442   run_wine_if_available "$LABEL shell32_argv" "$IMAGE" "$POD_ARCH" "$SHELL_EXE"
    443 
    444   # ---- comctl32 surface ----
    445   COMCTL_EXE=$work/comctl32_init-$LABEL.exe
    446   COMCTL_DUMP=$work/comctl32_init-$LABEL.dump
    447   build_and_check "$LABEL comctl32_init" "$COMCTL_C" "$COMCTL_EXE" \
    448     "$COMCTL_DUMP" console comctl32 COMCTL32.dll KERNEL32.dll -- \
    449     InitCommonControls InitCommonControlsEx
    450   run_wine_if_available "$LABEL comctl32_init" "$IMAGE" "$POD_ARCH" "$COMCTL_EXE"
    451 
    452   # ---- mixed libucrt.a (short-import + long-form helper) ----
    453   MIXED_EXE=$work/mixed_ucrt-$LABEL.exe
    454   MIXED_DUMP=$work/mixed_ucrt-$LABEL.dump
    455   build_and_check "$LABEL mixed_ucrt" "$MIXED_C" "$MIXED_EXE" "$MIXED_DUMP" \
    456     console "" KERNEL32.dll api-ms-win-crt-stdio-l1-1-0.dll -- fflush
    457   run_wine_if_available "$LABEL mixed_ucrt" "$IMAGE" "$POD_ARCH" "$MIXED_EXE"
    458 done
    459 
    460 if [ "$ran" -eq 0 ]; then
    461   skip_test "$LABEL_SUITE" "set KIT_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*"
    462 fi
    463 
    464 kit_summary "$LABEL_SUITE"
    465 kit_exit