-
Notifications
You must be signed in to change notification settings - Fork 153
Apps/ModuleLoadTest: add boot-time module load regression test #1666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
63053c5
Apps/ModuleLoadTest: add boot-time module load regression test
bghgary 901a593
TEMP: gate non-Win32 CI jobs with if: false for iteration
bghgary d087f0b
ModuleLoadTest: union CI-observed modules; scope CI to D3D11; run ste…
bghgary 44594ca
ModuleLoadTest: re-enable all Win32 build configs for module-delta co…
bghgary 6f70b36
ModuleLoadTest: revert TEMP CI iteration hacks
bghgary 9776057
ModuleLoadTest: extract shared Subtract/PrintList/CompareAndReport in…
bghgary a36b893
Apps/ModuleLoadTest: add Linux (X11) and macOS (Metal) platform support
bghgary 84cd839
CI: build and run ModuleLoadTest on Linux (xvfb) and macOS
bghgary e6c5c0b
ModuleLoadTest: seed macOS expected-module list from CI
bghgary b0f94f8
ModuleLoadTest: mirror UnitTests X11 setup to fix xvfb EGL surface fa…
bghgary e2bb676
ModuleLoadTest: seed Linux expected-module list from CI
bghgary ab61e05
Address review feedback: robust module enumeration + lifetimes
bghgary 2951f23
Address Copilot review on #1666
bghgary a422277
Factor out shared main() preflight into ShouldSkipEnvironment
bghgary ae7fa68
Move main() to shared App.cpp, platform files expose CreateGraphicsCo…
bghgary dfb8276
Note dbghelp.dll is in allow-list because bgfx loads it at boot
bghgary File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| set(SOURCES | ||
| "Source/App.h" | ||
| "Source/App.cpp" | ||
| "Source/ModuleSnapshot.h") | ||
|
|
||
| if(WIN32 AND NOT WINDOWS_STORE) | ||
| list(APPEND SOURCES | ||
| "Source/App.Win32.cpp" | ||
| "Source/ModuleSnapshot.Win32.cpp") | ||
| elseif(APPLE AND NOT IOS) | ||
| list(APPEND SOURCES | ||
| "Source/App.Apple.mm" | ||
| "Source/ModuleSnapshot.macOS.mm") | ||
| find_library(JAVASCRIPTCORE_LIBRARY JavaScriptCore) | ||
| set(ADDITIONAL_LIBRARIES PRIVATE ${JAVASCRIPTCORE_LIBRARY}) | ||
| elseif(UNIX AND NOT ANDROID) | ||
| list(APPEND SOURCES | ||
| "Source/App.X11.cpp" | ||
| "Source/ModuleSnapshot.Linux.cpp") | ||
| else() | ||
| # Unsupported platform (UWP, Android, iOS) — mobile/store bundling | ||
| # changes module-load semantics. Silently skip. | ||
| return() | ||
| endif() | ||
|
|
||
| add_executable(ModuleLoadTest ${SOURCES}) | ||
|
|
||
| target_link_libraries(ModuleLoadTest | ||
| PRIVATE AppRuntime | ||
| PRIVATE Blob | ||
| PRIVATE Canvas | ||
| PRIVATE Console | ||
| PRIVATE GraphicsDevice | ||
| PRIVATE NativeEngine | ||
| PRIVATE NativeEncoding | ||
| PRIVATE Window | ||
| PRIVATE XMLHttpRequest | ||
| ${ADDITIONAL_LIBRARIES}) | ||
|
|
||
| add_test(NAME ModuleLoadTest COMMAND ModuleLoadTest CONFIGURATIONS Release RelWithDebInfo) | ||
|
|
||
| # See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 | ||
| add_custom_command(TARGET ModuleLoadTest POST_BUILD | ||
| COMMAND ${CMAKE_COMMAND} -E $<IF:$<BOOL:$<TARGET_RUNTIME_DLLS:ModuleLoadTest>>,copy,true> $<TARGET_RUNTIME_DLLS:ModuleLoadTest> $<TARGET_FILE_DIR:ModuleLoadTest> COMMAND_EXPAND_LISTS) | ||
|
|
||
| set_property(TARGET ModuleLoadTest PROPERTY FOLDER Apps) | ||
| source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| #include "App.h" | ||
| #include "ModuleSnapshot.h" | ||
|
|
||
| #include <Babylon/DebugTrace.h> | ||
| #include <Babylon/Graphics/Device.h> | ||
|
|
||
| #import <Metal/Metal.hpp> | ||
|
|
||
| #include <sys/sysctl.h> | ||
| #include <sys/types.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include <iostream> | ||
| #include <string_view> | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| namespace ModuleLoadTest | ||
| { | ||
| // Apple equivalent of IsDebuggerPresent() — non-invasive. | ||
| // https://developer.apple.com/library/archive/qa/qa1361/_index.html | ||
| bool IsBeingTraced() | ||
| { | ||
| int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; | ||
| struct kinfo_proc info{}; | ||
| size_t size = sizeof(info); | ||
| if (sysctl(name, 4, &info, &size, nullptr, 0) != 0) | ||
| { | ||
| return false; | ||
| } | ||
| return (info.kp_proc.p_flag & P_TRACED) != 0; | ||
| } | ||
|
|
||
| const ModuleSnapshot& GetExpectedBootModules() | ||
| { | ||
| // Seeded from CI on macos-latest (ARM64 paravirtualized GPU runner). | ||
| // Append entries here as new configs surface deltas. | ||
| static const ModuleSnapshot kModules{ | ||
| "appleparavirtgpumetaliogpufamily", | ||
| "iogpu", | ||
| }; | ||
| return kModules; | ||
| } | ||
|
|
||
| // On macOS the interesting per-SKU variation is the Metal/GPU driver | ||
| // bundles (AMDMTLBronzeDriver, AppleIntelKBLGraphicsMTLDriver, ...), which | ||
| // dyld loads by path under /System/Library/Extensions/. Base names still | ||
| // differ between Apple Silicon and Intel. Seed a broad carve-out. | ||
| bool IsAllowedOptionalModule(std::string_view name) | ||
| { | ||
| static constexpr std::string_view kPrefixes[] = { | ||
| // Metal GPU driver bundles | ||
| "amdmtl", | ||
| "appleintel", | ||
| "applem1", | ||
| "applem2", | ||
| "applem3", | ||
| "nvmtl", | ||
| // Ambient/IOSurface layer | ||
| "iosurface", | ||
| }; | ||
| for (const auto& prefix : kPrefixes) | ||
| { | ||
| if (name.size() >= prefix.size() && name.compare(0, prefix.size(), prefix) == 0) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| std::optional<Babylon::Graphics::Configuration> CreateGraphicsConfig() | ||
| { | ||
| // MTL::Device must outlive RunBoot. Park in function-local static | ||
| // storage so it lives for the duration of the process. | ||
| static NS::SharedPtr<MTL::Device> device = NS::TransferPtr(MTL::CreateSystemDefaultDevice()); | ||
| if (!device) | ||
| { | ||
| std::cout << "ModuleLoadTest: SKIP - no Metal device available." << std::endl; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| Babylon::DebugTrace::EnableDebugTrace(true); | ||
| Babylon::DebugTrace::SetTraceOutput([](const char* trace) { NSLog(@"%s", trace); }); | ||
|
|
||
| Babylon::Graphics::Configuration config{}; | ||
| config.Device = device.get(); | ||
| config.Width = 600; | ||
| config.Height = 400; | ||
| return config; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| #include "App.h" | ||
| #include "ModuleSnapshot.h" | ||
|
|
||
| #include <Babylon/DebugTrace.h> | ||
| #include <Babylon/Graphics/Device.h> | ||
|
|
||
| #include <Windows.h> | ||
|
|
||
| #include <iostream> | ||
| #include <string_view> | ||
|
|
||
| namespace ModuleLoadTest | ||
| { | ||
| namespace | ||
| { | ||
| LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) | ||
| { | ||
| return ::DefWindowProc(hWnd, msg, wParam, lParam); | ||
| } | ||
| } | ||
|
|
||
| bool IsBeingTraced() | ||
| { | ||
| return ::IsDebuggerPresent() != FALSE; | ||
| } | ||
|
|
||
| // Expected set of modules loaded during BabylonNative boot, as a delta | ||
| // from the baseline snapshot captured by the TLS callback before any C++ | ||
| // static initializer in this binary has run. Base names only, lower case. | ||
| // | ||
| // This list targets optimized builds (Release and RelWithDebInfo, which | ||
| // produce identical module sets). Debug builds load additional debug CRT | ||
| // and diagnostic DLLs and are not supported — main() returns a SKIP in | ||
| // that config. CI runs only in RelWithDebInfo (see CMakeLists.txt's | ||
| // `add_test ... CONFIGURATIONS Release RelWithDebInfo`). | ||
| // | ||
| // Launch-environment noise (e.g. VS's Start Without Debugging injecting | ||
| // kernel.appcore.dll) is filtered via IsAllowedOptionalModule so devs | ||
| // see the same verdict from Ctrl-F5 as CI does from a plain cmd launch. | ||
| // | ||
| // The assertion is asymmetric: we FAIL on modules not in this list (a new | ||
| // module was pulled in — the regression signal we want), and we IGNORE | ||
| // modules in this list that did not load (environmental variance across | ||
| // GPU SKUs, Windows patch levels, VS vs cmd launch, etc.). This lets the | ||
| // list be a permissive superset that works on both dev machines and CI. | ||
| // | ||
| // Motivating example: a dependency quietly pulling in dbghelp.dll on boot. | ||
| // dbghelp.dll lives in System32, so a path-based OS filter would miss it; | ||
| // we therefore golden-list the full delta (including OS modules) and only | ||
| // allow a narrow name-pattern carve-out for GPU driver ICDs whose exact | ||
| // names differ per runner SKU (see IsAllowedOptionalModule). | ||
| const ModuleSnapshot& GetExpectedBootModules() | ||
| { | ||
| // Seeded from a local RelWithDebInfo run on Windows 11 x64 with D3D11 | ||
| // and Chakra. CI may add more entries for other Win32 configs | ||
| // (V8/JSI/D3D12) — those should be appended as the draft PR runs. | ||
| static const ModuleSnapshot kModules{ | ||
| "bcryptprimitives.dll", | ||
| "cfgmgr32.dll", | ||
| "crypt32.dll", | ||
| "cryptbase.dll", | ||
| "cryptnet.dll", | ||
| "d3d10warp.dll", | ||
| "d3d11.dll", | ||
| "d3d11_3sdklayers.dll", | ||
| "d3d12.dll", | ||
| "d3d12core.dll", | ||
| "d3d12sdklayers.dll", | ||
| "d3dscache.dll", | ||
| // TODO: bgfx loads dbghelp.dll at boot (callstack/crash helper). Drop this | ||
| // entry once bgfx stops pulling it in. | ||
| "dbghelp.dll", | ||
|
bghgary marked this conversation as resolved.
|
||
| "dcomp.dll", | ||
| "devobj.dll", | ||
| "directxdatabasehelper.dll", | ||
| "drvstore.dll", | ||
| "dwmapi.dll", | ||
| "dxcore.dll", | ||
| "dxgi.dll", | ||
| "dxgidebug.dll", | ||
| "dxilconv.dll", | ||
| "iertutil.dll", | ||
| "imagehlp.dll", | ||
| "msasn1.dll", | ||
| "msctf.dll", | ||
| "netutils.dll", | ||
| "ntmarta.dll", | ||
| "powrprof.dll", | ||
| "profapi.dll", | ||
| "rsaenh.dll", | ||
| "setupapi.dll", | ||
| "shcore.dll", | ||
| "shell32.dll", | ||
| "srvcli.dll", | ||
| "umpdc.dll", | ||
| "userenv.dll", | ||
| "uxtheme.dll", | ||
| "version.dll", | ||
| "windows.storage.dll", | ||
| "winmm.dll", | ||
| "wintrust.dll", | ||
| "wintypes.dll", | ||
| "wldp.dll", | ||
| }; | ||
| return kModules; | ||
| } | ||
|
|
||
| // Name patterns for modules whose presence in the delta is allowed but | ||
| // not required. Covers two classes: | ||
| // | ||
| // * GPU driver ICDs, whose exact base name depends on which GPU the | ||
| // runner has (NVIDIA/Intel/AMD/ATI). | ||
| // * Environmental/ambient DLLs that some launchers (e.g. Visual Studio's | ||
| // Start Without Debugging) inject into the process but which a plain | ||
| // cmd.exe launch does not. These are not introduced by BabylonNative. | ||
| // | ||
| // Prefixes match at the start of the base name; exacts match the whole | ||
| // name. | ||
| bool IsAllowedOptionalModule(std::string_view name) | ||
| { | ||
| static constexpr std::string_view kPrefixes[] = { | ||
| // GPU driver ICDs | ||
| "nv", // NVIDIA (nvoglv64.dll, nvapi64.dll, nvd3dum.dll, ...) | ||
| "ig", // Intel (igdumd64.dll, igxelpicd64.dll, ...) | ||
| "amd", // AMD (amdvlk64.dll, amdxc64.dll, ...) | ||
| "atio", // AMD/ATI (atio6axx.dll, ...) | ||
| // Ambient WARP variants | ||
| "microsoft.internal.warppal", | ||
| }; | ||
| static constexpr std::string_view kExact[] = { | ||
| // GPU driver ICD | ||
| "dxil.dll", | ||
| // VS Start-Without-Debugging launch environment | ||
| "kernel.appcore.dll", | ||
| }; | ||
| for (const auto& prefix : kPrefixes) | ||
| { | ||
| if (name.size() >= prefix.size() && name.compare(0, prefix.size(), prefix) == 0) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| for (const auto& exact : kExact) | ||
| { | ||
| if (name == exact) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| std::optional<Babylon::Graphics::Configuration> CreateGraphicsConfig() | ||
| { | ||
| ::SetConsoleOutputCP(CP_UTF8); | ||
|
|
||
| // bgfx D3D12 implementation requires an HWND to avoid a device refcount | ||
| // leak on shutdown. Create a hidden window to satisfy that requirement. | ||
| // Parked in function-local static storage so the handle lives for the | ||
| // duration of the process. | ||
| static WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, | ||
| ::GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, | ||
| "BabylonNativeModuleLoadTest", nullptr }; | ||
| ::RegisterClassEx(&wc); | ||
| static HWND hWnd = ::CreateWindow(wc.lpszClassName, "BabylonNativeModuleLoadTest", | ||
| WS_OVERLAPPEDWINDOW, -1, -1, -1, -1, nullptr, nullptr, wc.hInstance, nullptr); | ||
|
|
||
| Babylon::DebugTrace::EnableDebugTrace(true); | ||
| Babylon::DebugTrace::SetTraceOutput([](const char* trace) { | ||
| ::OutputDebugStringA(trace); | ||
| ::OutputDebugStringA("\n"); | ||
| }); | ||
|
|
||
| Babylon::Graphics::Configuration config{}; | ||
| config.Window = hWnd; | ||
| config.Width = 600; | ||
| config.Height = 400; | ||
| return config; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.