kit

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

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


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