This is an automated email from the ASF dual-hosted git repository. leginee pushed a commit to branch bazel-migration in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit a03189dcea08fd90cad2a9843a32e97aeedbab58 Author: Peter Kovacs <[email protected]> AuthorDate: Wed Aug 5 06:09:54 2026 +0200 test: wire sal child-process suites + the OfficeConnection fixture Two gtest_test capabilities, and the last two blockers on the qa/ sweep. bin_layout — the sal child-process suites (osl/process, rtl/process, rtl/bootstrap) were long recorded as unwirable "without source changes", on the reading that they look their helper exe up at ../bin. They don't: each derives the helper's directory as dir-of(own module) -> strip the last component -> + "bin", which under dmake's solver/bin was the IDENTITY, not a sibling lookup. Naming the staging dir "bin" reproduces it exactly. Helpers come from a new sal_qa_helper_exe macro (same compile environment as sal_qa_test, minus gtest) and are staged via gtest_test's `companions`. Unrelated second landmine in the same suites: both osl/process TUs use LPTSTR / GetEnvironmentStrings / _tcslen without including <windows.h>, and nothing they do include reaches it — bit-rot, since dmake gates every qa/ dir behind ENABLE_UNIT_TESTS=NO. Supplied with /FIwindows.h, but the two want OPPOSITE flavours and swapping them compiles cleanly while silently walking an ANSI block as wide chars: parent = ANSI, child = /DUNICODE= /D_UNICODE= (empty, so its own #define is an identical redefinition rather than a C4005). office_connection — fixture (b), test::OfficeConnection: launch a real soffice with -accept=...;urp and resolve a remote context. The launcher makes a throwaway user installation and exports arg-soffice / arg-user. There is NOTHING to port from dmake here: solenv's C++ APP1TEST rule runs the bare exe with only --gtest_output and never sets these; the complete recipe is the JAVA one (installationtest.mk::javatest), which this mirrors, with two forced divergences. (1) The args go in the ENVIRONMENT, not as -env: command-line args — rtl::Bootstrap tries the command line first, but that half reads osl_getCommandArgCount(), only populated by SAL_IMPLEMENT_MAIN, and AOO's gtest suites all declare a bare main(). (2) arg-user is a NATIVE PATH, not Java's file:// URL, because the C++ side feeds it to getFileURLFromSystemPath(). LANDMINE: a backslash is an ESCAPE CHARACTER in any rtl::Bootstrap value (every value goes through macro expansion; read() in sal's bootstrap.cxx turns \X into X), so a raw Windows path loses EVERY separator — "C:\Users\x" comes back "C:Usersx". Doubled in the launcher. LANDMINE, inverting the usual runtime_dlls advice: with uno_install, co-locating a core UNO DLL BREAKS the bootstrap. cppuhelper picks the dir to load bootstrap.uno.dll from via get_this_libpath() = getUrlFromAddress on ITSELF, and the exe's own dir beats PATH, so a co-located cppuhelper3MSC.dll makes it search the test dir. List only test-only DLLs; the office closure is already on PATH via program/. The fixture ships with a MIGRATION-AUTHORED smoke test (main/test/qa/test_officeconnection.cxx). Upstream's only C++ consumer is xmlsecurity/qa/certext, which cannot build here — it calls ne_ssl_cert_read() from neon, which AOO replaced with curl and removed from the tree — so without this the fixture would ship unexercised. Green: osl_process 7/8, rtl_Process 3/3, rtl_Bootstrap 25/30, //main/test:test_qa_officeconnection 3/3 (~15s). Residual reds are test defects, not build: osl_process asserts an environment ORDER Windows doesn't use, and rtl_Bootstrap expects the default ini to be testshl2.ini because under the retired testshl2 harness the process literally was testshl2.exe. This closes the qa/ sweep: no already-migrated module still has an unwired C++ qa/ suite. Co-Authored-By: Claude Opus 5 <[email protected]> --- CLAUDE.md | 76 +++++++++++++++--- build/rules/gtest_test.bzl | 116 +++++++++++++++++++++++++-- main/sal/BUILD.bazel | 111 +++++++++++++++++++++----- main/sal/sal_qa.bzl | 49 +++++++++--- main/test/BUILD.bazel | 78 ++++++++++++++++++ main/test/qa/test_officeconnection.cxx | 141 +++++++++++++++++++++++++++++++++ main/test/readme.md | 118 ++++++++++++++++++++++++--- 7 files changed, 629 insertions(+), 60 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ba0552af62..ce1d4525b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,8 +48,13 @@ test 🔨 C++ unit-test infra runnable — NOW THE FRONT-LINE TASK: br EMPTY test.log; and the CRT manifest must be EMBEDDED (RT_MANIFEST id 1, now linked into every gtest_test exe) not just staged as an external <exe>.manifest, or late DLL loads fall outside the - activation context. NEXT: sweep qa/ across the remaining migrated - modules. DONE since: 8 sal osl/socket suites + shell_qa_zip. + activation context. The qa/ SWEEP IS DONE: no ALREADY-MIGRATED module + still has an unwired C++ qa/ suite (verified 2026-08-04 — every other + qa/ dir is Java, or Perl/shell tooling as in basegfx+slideshow; the + only C++ leftover is xmlsecurity/qa/certext, which needs fixture (b)). + DONE since: 8 sal osl/socket suites + shell_qa_zip + the 3 sal + child-process suites (osl_process 7/8, rtl_Process 3/3, + rtl_Bootstrap 25/30). "Needs a CppUnit external dep" was largely a MYTH — NOTHING checked so far actually uses cppunit. osl/socket and shell/qa are plain GoogleTest (now wired); writerfilter/qa/cppunittests is misnamed — @@ -76,10 +81,42 @@ test 🔨 C++ unit-test infra runnable — NOW THE FRONT-LINE TASK: br //main/svl:svl_qa_test_URIHelper. Caveat — it depends on the WHOLE install, so it is slow and not a unit test; only use uno_install where UNO is genuinely bootstrapped. (b) test::OfficeConnection - (launch soffice -accept=…;urp, resolve over URP) is STILL UNWIRED - and is the remaining front line — test.dll is built and its args - come from rtl::Bootstrap (arg-soffice=path:…, arg-user=…), so what - is missing is the process lifecycle, not the plumbing. + (launch soffice -accept=…;urp, resolve over URP) is DONE 2026-08-05: + gtest_test office_connection=True (+ uno_install) makes a throwaway + user installation and exports arg-soffice/arg-user. GREEN: + //main/test:test_qa_officeconnection (3/3 runs, ~15s — real office + boot → URP resolve → remote Desktop → clean terminate). + NOTHING TO PORT from dmake: solenv's C++ APP1TEST rule (_tg_app.mk) + runs the bare exe with only --gtest_output and never sets these args + at all; the complete recipe is the JAVA one + (installationtest.mk::javatest), which is what was mirrored. Two + forced divergences from it: (1) the args go in the ENVIRONMENT, not + as -env: command-line args — rtl::Bootstrap tries the command line + first, but that half reads osl_getCommandArgCount(), only populated + by SAL_IMPLEMENT_MAIN, and AOO's gtest suites all declare a bare + main() (same root cause as //build/testsupport:sal_process_init); + (2) arg-user is a NATIVE PATH, not Java's file:// URL, because the + C++ side feeds it to getFileURLFromSystemPath(). + LANDMINE: a BACKSLASH IS AN ESCAPE CHARACTER in any rtl::Bootstrap + value (every value goes through macro expansion; read() in sal + bootstrap.cxx turns \X into X), so a raw Windows path loses EVERY + separator — "C:\Users\x" comes back "C:Usersx" and + getFileURLFromSystemPath fails with 21. Double them. + LANDMINE (INVERTS the usual runtime_dlls advice): with uno_install, + co-locating a core UNO DLL BREAKS the bootstrap. cppuhelper picks + the dir to load bootstrap.uno.dll from via get_this_libpath() = + getUrlFromAddress on ITSELF, and the exe's own dir beats PATH — so a + co-located cppuhelper3MSC.dll makes it search the TEST dir ("loading + component library failed: …/<test>.run/bootstrap.uno.dll"). List + only test-only DLLs; the office closure is already on PATH via + program/ (svl_qa_test_URIHelper lists none, which is why it passed). + CONSUMER NOTE: upstream's ONLY C++ user of the fixture, + xmlsecurity/qa/certext, CANNOT BUILD — it needs neon + (ne_ssl_cert_read) and AOO replaced neon with curl (no main/neon, no + ext module, no neon in configure.in, ucb webdav is curl-based; only a + stale NEON3RDLIB lingers in solenv/inc/libs.mk). Hence the + migration-authored smoke test, without which the fixture would ship + unexercised. LANDMINE for any new launcher: bazel test's CWD is NEITHER the exe dir NOR the execroot — locate everything from %~dp0 (see _windows_relpath in gtest_test.bzl). @@ -107,10 +144,29 @@ test 🔨 C++ unit-test infra runnable — NOW THE FRONT-LINE TASK: br env, not just uno_install=//main/staging:install. • cppuhelper/qa/propertysetmixin — UNO component DLL + own types.idl + a per-test services.rdb (packcomponents.xslt). - • xmlsecurity/qa/certext — uses test::OfficeConnection ⇒ fixture (b). - • sal child-process suites (osl/process, rtl/bootstrap, - rtl/process) — helper exe via getExecutablePath()+"/../bin"; - gtest_test's `companions` hook exists for this. + • xmlsecurity/qa/certext — BLOCKED, and NOT on fixture (b) as + recorded here before: it #includes <neon/ne_ssl.h> and calls + ne_ssl_cert_read(), but AOO deleted neon in favour of curl. Would + need a neon external wrap (or a source change) — do not treat it as + a fixture-(b) consumer. + DONE 2026-08-04 — sal child-process suites (osl/process, rtl/process, + rtl/bootstrap). The recorded blocker ("helper exe via + getExecutablePath()+/../bin, needs source changes") was a MISREADING: + the idiom is dir-of(own module) → strip last component → +"bin", which + under dmake's solver/bin is the IDENTITY, not a sibling lookup. New + gtest_test `bin_layout` just names the staging dir "bin" and it + resolves; helpers via sal_qa_helper_exe + `companions`. Second + landmine, unrelated: BOTH osl/process TUs use LPTSTR/ + GetEnvironmentStrings/_tcslen without including <windows.h> (nothing + they include reaches it — precompiled_sal.hxx is empty), i.e. bit-rot, + since dmake gates qa/ behind ENABLE_UNIT_TESTS=NO. Fixed with + /FIwindows.h, but the two need OPPOSITE flavours and swapping them + compiles cleanly while walking an ANSI block as wide chars: parent = + ANSI, child = /DUNICODE= /D_UNICODE= (empty, so its own #define is an + identical redefinition, not C4005). Residual reds are test defects, + not build: osl_process asserts an env ORDER Windows doesn't use; + rtl_Bootstrap expects the default ini to be testshl2.ini because the + testshl2-era process was literally testshl2.exe. testtools ⬜ (bridgetest — pure-C++ UNO bridge round-trip; cli/pyuno/java variants need rules_java — see Java bucket) qadevOOo 🔨 OOoRunner.jar built (//main/qadevOOo:OOoRunner — qadevOOo QA diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl index 1424e1b272..9b100ec977 100644 --- a/build/rules/gtest_test.bzl +++ b/build/rules/gtest_test.bzl @@ -60,7 +60,16 @@ def _windows_relpath(from_dir, to_dir): return "..\\" * (len(f) - common) + "\\".join(t[common:]) def _staged_gtest_test_impl(ctx): - d = ctx.label.name + ".run" + # The staging dir is normally "<name>.run". bin_layout makes it + # "<name>.run/bin" instead — the ONE thing the child-process suites need. + # They locate their helper exe with the testshl2-era idiom + # dir-of(own module) → strip last path component → + "bin" + # (osl_process.cxx::getExecutablePath, rtl_Process/rtl_Bootstrap:: + # getModulePath). Under dmake both parent and child lived in solver/bin, so + # that round-trip was the IDENTITY; it only looks like an "../bin" lookup. + # Naming the staging dir "bin" reproduces the identity exactly, so the + # helpers staged beside the exe are found with no source change. + d = ctx.label.name + ".run" + ("/bin" if ctx.attr.bin_layout else "") staged = [] seen = {} @@ -144,6 +153,11 @@ def _staged_gtest_test_impl(ctx): ]: stage(f) + if ctx.attr.office_connection and not uno_program_dir: + fail("office_connection requires uno_install — test::OfficeConnection " + + "bootstraps an in-process context to build the URL resolver, and " + + "the soffice it launches comes from the staged install.") + executable = staged_exe if ctx.attr.run_in_staged_dir or uno_program_dir: launcher_dir = staged_exe.dirname # the .bat sits beside the staged exe @@ -160,9 +174,68 @@ def _staged_gtest_test_impl(ctx): # live in program/; the exe's own directory still wins for what it # imports directly, so its staged copies are unaffected. 'set "PATH=%_PROG%;%PATH%"', - 'cd /d "%_PROG%" || exit /b 1', - '"%_EXE%" %*', ] + + # Working directory. program/ by default (what the UNO bootstrap + # tests have always used), but the staged dir when the test also + # opens a fixture by bare relative name — the two demands collide, + # and PATH already covers the only reason program/ was the cwd. + if ctx.attr.run_in_staged_dir: + lines += ['cd /d "%~dp0" || exit /b 1'] + else: + lines += ['cd /d "%_PROG%" || exit /b 1'] + + if ctx.attr.office_connection: + # test::OfficeConnection reads its two arguments through + # rtl::Bootstrap under an "arg-" prefix (test/getargument.cxx). + # There is no dmake recipe to copy: solenv's C++ APP1TEST rule + # runs the bare exe with only --gtest_output, so it never sets + # these at all; the complete recipe is the JAVA one + # (installationtest.mk::javatest), which this mirrors. + # * arg-soffice — "path:<exe>" makes OfficeConnection LAUNCH an + # office (with -accept=pipe,name=…;urp); "connect:<desc>" + # would attach to an already-running one. + # * arg-user — a fresh user installation, wiped before AND after + # as javatest does, so no state leaks between runs. NOTE it + # is a NATIVE PATH, not the file:// URL the Java side passes: + # the C++ side feeds it to toAbsoluteFileUrl(), i.e. + # osl::FileBase::getFileURLFromSystemPath(). + # + # Passed as ENVIRONMENT VARIABLES, not as "-env:arg-…" command + # line arguments, which is what the dmake/Java side implies. + # rtl::Bootstrap resolves a key by trying the command line first + # and the environment second (bootstrap.cxx, Bootstrap_Impl:: + # getValue), but the command-line half reads + # osl_getCommandArgCount(), which is only ever populated by + # sal_detail_initialize() — i.e. by SAL_IMPLEMENT_MAIN. AOO's + # GoogleTest suites declare a plain main() instead and skip that, + # so -env: arguments are INVISIBLE to them. Same root cause as + # //build/testsupport:sal_process_init (no WSAStartup for the + # socket suites); the environment route needs no shim TU and + # cannot be broken by a future suite's choice of main(). + lines += [ + 'set "_USER=%TEST_TMPDIR%\\oootest_user"', + 'if "%TEST_TMPDIR%"=="" set "_USER=%~dp0oootest_user"', + # TEST_TMPDIR arrives with forward slashes; osl's + # getFileURLFromSystemPath wants native separators. + 'set "_USER=%_USER:/=\\%"', + 'if exist "%_USER%" rmdir /s /q "%_USER%"', + 'mkdir "%_USER%" || exit /b 1', + 'set "_SOFFICE=%_PROG%\\soffice.exe"', + # A BACKSLASH IS AN ESCAPE CHARACTER in a bootstrap value: + # rtl::Bootstrap runs every value it returns through macro + # expansion, where read() (sal/rtl/source/bootstrap.cxx) + # turns "\X" into "X" and "\uXXXX" into a code point. So a + # raw Windows path loses EVERY separator on the way out — + # "C:\Users\x" comes back as "C:Usersx" and + # getFileURLFromSystemPath then fails with 21. Double them + # so one survives each unescape. (The dmake/Java side never + # hit this: it passes a file:// URL, which has no + # backslashes.) + 'set "arg-soffice=path:%_SOFFICE:\\=\\\\%"', + 'set "arg-user=%_USER:\\=\\\\%"', + ] + lines += ['"%_EXE%" %*'] else: # Co-locating a data file with the exe is not enough for a test that # opens it by bare relative name: the working directory is the @@ -172,7 +245,12 @@ def _staged_gtest_test_impl(ctx): 'cd /d "%~dp0" || exit /b 1', '"%~dp0' + staged_exe.basename + '" %*', ] - lines += ["exit /b %ERRORLEVEL%", ""] + + # Capture the exit code BEFORE any cleanup — rmdir would clobber it. + lines += ['set "_RC=%ERRORLEVEL%"'] + if ctx.attr.office_connection: + lines += ['rmdir /s /q "%_USER%" 2>nul'] + lines += ["exit /b %_RC%", ""] launcher = ctx.actions.declare_file(d + "/" + ctx.label.name + "_run.bat") ctx.actions.write(output = launcher, content = "\r\n".join(lines), is_executable = True) @@ -194,6 +272,8 @@ _staged_gtest_test = rule( "companions": attr.label_list(cfg = "target"), "app_manifest": attr.label(allow_single_file = True, default = _APP_MANIFEST), "run_in_staged_dir": attr.bool(default = False), + "bin_layout": attr.bool(default = False), + "office_connection": attr.bool(default = False), "uno_install": attr.label(allow_files = True), }, ) @@ -215,9 +295,11 @@ def gtest_test( data_files = [], uno_install = None, companions = [], + bin_layout = False, + office_connection = False, additional_linker_inputs = [], linkopts = [], - size = "small", + size = None, **kwargs): """A GoogleTest suite that actually runs under `bazel test` on Windows/MD. @@ -237,7 +319,27 @@ def gtest_test( sfx.dll or sw.dll. It does make the test depend on the entire install, but a slow test beats no test; build time is not a reason to skip wiring something. + + companions: helper exes the test spawns by name (a child process). Staged + beside the exe with their own external CRT manifest. + + bin_layout: stage into "<name>.run/bin" instead of "<name>.run", for suites + that derive the helper's directory as <parent-of-own-dir>/bin (see the + staging rule). Implies run_in_staged_dir so the child's working directory + matches too. + + office_connection: the test uses test::OfficeConnection, i.e. it LAUNCHES a + real soffice and talks to it over URP (fixture (b)). Requires uno_install. + Supplies arg-soffice / arg-user and a per-run user installation. + Defaults size to "medium" (300s): an office boot is far past the "small" + 60s budget — ~15s observed here, and every run builds a FRESH user + installation so it is always a cold start. The headroom also bounds the + failure mode: OfficeConnection::setUp() retries the resolve in an UNBOUNDED + loop, so if the office never comes up the test timeout is the only thing + that ends it. Do not drop this to "small". """ + if size == None: + size = "medium" if office_connection else "small" cc_binary( name = name + "_bin", srcs = srcs, @@ -263,7 +365,9 @@ def gtest_test( binary = ":" + name + "_bin", runtime = runtime_dlls + data_files + [_CRT], companions = companions, - run_in_staged_dir = bool(data_files), + run_in_staged_dir = bool(data_files) or bin_layout, + bin_layout = bin_layout, + office_connection = office_connection, uno_install = uno_install, size = size, ) diff --git a/main/sal/BUILD.bazel b/main/sal/BUILD.bazel index ee152cc1f3..8f6c5e60f9 100644 --- a/main/sal/BUILD.bazel +++ b/main/sal/BUILD.bazel @@ -2,7 +2,7 @@ package(default_visibility = ["//visibility:public"]) # we need to load cc_binary and cc_library to be able to use them here load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") -load("//main/sal:sal_qa.bzl", "sal_qa_test") +load("//main/sal:sal_qa.bzl", "sal_qa_helper_exe", "sal_qa_test") # Global defines for all cc_library rules in sal (Windows MSVC) _COPTS = [ @@ -200,19 +200,16 @@ filegroup( # only real gate is the test source's helper includes — all handled by the # macro's /Imain/sal/qa/inc + per-dir include. # -# DEFERRED (not wired): -# * cppunit suites — osl/socket/*, rtl_strings/* → Phase 4 (needs CppUnit dep) -# * child-process tests — osl/process, rtl/bootstrap, rtl/process (parent -# execs a helper exe that would need separate staging) -# * resource/IPC tests — osl/file, osl/module, osl/pipe (need temp dirs, a -# loadable module, or a process pair) -# * systools/test_comtools — Win32 COM smart-pointers (needs ole32 + CoInit) -# * osl/security — Win32 SID/registry APIs (needs <windows.h> + advapi32.lib) +# DEFERRED (not wired): osl/security only — see the note at its commented-out +# sal_qa_test below. Everything else once listed here is now wired: the +# osl/socket suites (plain GoogleTest all along, + sal_process_init), the +# resource/IPC tests (osl/file, osl/module, osl/pipe), systools/test_comtools, +# and the child-process spawners (osl/process, rtl/process, rtl/bootstrap — +# see the bin_layout note below). # # KNOWN UPSTREAM FAILURES (wired + run, but fail on their own merits — source is -# out of scope so not fixed here): testHelperFunctions has 64-bit-shift logic -# asserts that fail on this platform. These remain buildable/runnable -# individually. +# out of scope so not fixed here): see the annotated list next to the test_suite +# at the bottom of this file. All of them remain buildable/runnable individually. # # FIXED: rtl_str / rtl_ustr / rtl_string previously had *_000 cases that passed # NULL into C string functions which dereference it (e.g. rtl_str_compare(NULL, @@ -268,6 +265,74 @@ sal_qa_test(name = "osl_old_test_file", srcs = ["qa/osl/file/osl_old_test_file.c # osl_Thread calls Win32 Sleep() (kernel32, default-linked): force-include <windows.h>. sal_qa_test(name = "osl_Thread", srcs = ["qa/osl/process/osl_Thread.cxx"], subdir = "osl/process", copts = ["/FIwindows.h"]) +# ── child-process suites ───────────────────────────────────────────────── +# Long listed here as unwirable "without source changes", on the reading that +# they look their helper exe up at ../bin. They don't. Each derives the +# helper's directory as +# dir-of(own module) → strip the last path component → append "bin" +# (osl_process.cxx::getExecutablePath, rtl_Process.cxx / rtl_Bootstrap.cxx:: +# getModulePath). Under dmake parent and child both lived in solver/bin, so +# that round-trip resolved back to the SAME directory — it is the identity, not +# a sibling lookup. gtest_test's `bin_layout` reproduces it by naming the +# staging dir "bin"; the helpers then sit right where the suites look, with the +# source untouched. (bin_layout also runs the test with its working directory +# in the staged dir, which the environment-dump cases need.) +# +# Both TUs of the osl/process pair walk the environment block with LPTSTR / +# GetEnvironmentStrings() / _tcslen() but neither includes <windows.h>, and +# nothing they do include reaches it (sal's precompiled_sal.hxx is empty and no +# rtl header pulls it in) — dead code, since dmake gates every qa/ dir behind +# ENABLE_UNIT_TESTS, which defaults to NO. Source is out of scope, so supply it +# with /FIwindows.h — but the two want OPPOSITE flavours, and getting that +# backwards compiles cleanly while silently walking an ANSI block as wide chars: +# * osl_process (parent) defines neither UNICODE nor _UNICODE, and does +# std::string(p) / _tcslen(p) on the LPTSTR → it wants the ANSI mapping. +# * osl_process_child defines UNICODE + _UNICODE itself and does +# reinterpret_cast<wchar_t*> + WideCharToMultiByte → it wants the wide one, +# so UNICODE must already be defined when windows.h arrives. Defined as +# EMPTY (/DUNICODE=) so the source's own `#define UNICODE` is an identical +# redefinition rather than a C4005. +sal_qa_helper_exe( + name = "osl_process_child", + srcs = ["qa/osl/process/osl_process_child.cxx"], + subdir = "osl/process", + copts = ["/DUNICODE=", "/D_UNICODE=", "/FIwindows.h"], +) +sal_qa_test( + name = "osl_process", + srcs = ["qa/osl/process/osl_process.cxx"], + subdir = "osl/process", + copts = ["/FIwindows.h"], + companions = [":osl_process_child"], + bin_layout = True, +) + +sal_qa_helper_exe(name = "child_process", srcs = ["qa/rtl/process/child_process.cxx"], subdir = "rtl/process") +sal_qa_helper_exe(name = "child_process_id", srcs = ["qa/rtl/process/child_process_id.cxx"], subdir = "rtl/process") +sal_qa_test( + name = "rtl_Process", + srcs = ["qa/rtl/process/rtl_Process.cxx"], + subdir = "rtl/process", + companions = [ + ":child_process", + ":child_process_id", + ], + bin_layout = True, +) + +# rtl_Bootstrap additionally writes its own bootstrap_process.ini next to the +# exe at run time (create_bootstrap_processrc) and reads testshl2.ini/pseudo.ini +# from the exe's directory via osl_getExecutableFile — all satisfied by the same +# staged dir, no extra data files. +sal_qa_helper_exe(name = "bootstrap_process", srcs = ["qa/rtl/bootstrap/bootstrap_process.cxx"], subdir = "rtl/bootstrap") +sal_qa_test( + name = "rtl_Bootstrap", + srcs = ["qa/rtl/bootstrap/rtl_Bootstrap.cxx"], + subdir = "rtl/bootstrap", + companions = [":bootstrap_process"], + bin_layout = True, +) + # osl/socket — 8 apps from qa/osl/socket/makefile.mk. These were long listed as # "blocked on a CppUnit external dep"; that was wrong — every one of them is # already a plain GoogleTest suite (`#include "gtest/gtest.h"`, TEST_F) with no @@ -349,21 +414,22 @@ sal_qa_test(name = "sal_ut_bytesequence", srcs = ["qa/ByteSequence/ByteSequence. # test — passing and failing alike (failures are information, not hidden). Most # pass; the known failures fail on their OWN merits, NOT the build (source is out # of scope), and are kept in so they stay visible: -# testHelperFunctions — test_t_abs64 does (2147483648 << 1) in 32-bit → 0 # rtl_logfile — writes/reads c:/temp and asserts on it → env/permission bound # rtl_textcvt — conversion/mime/codepage table expectations (sal conversion # works in the running app → test-data drift) # osl_File / osl_old_test_file / tcwf / osl_Module / sal_ut_osl_pipe — exercise # the filesystem / module loader / pipes; may depend on the run environment +# osl_process — osl_execProc_merged_child_environment compares the merged vars +# against a hand-written ORDER, but Windows returns the environment block +# sorted case-insensitively (PAT, Patha, PATHb ≠ PAT, PATHb, Patha) +# rtl_Bootstrap — 4 of 5 read the DEFAULT bootstrap context and expect +# testshl2.ini; the default ini is <exename>.ini, and under the retired +# testshl2 harness the process was literally testshl2.exe # -# NOT YET WIRED (hard blockers, not cosmetic): -# * cppunit suites — osl/socket/*, rtl_strings/* → need a CppUnit external dep -# (Phase 4); they will not compile until it is wrapped. -# * child-process spawners — osl/process:osl_process, rtl/process:rtl_Process, -# rtl/bootstrap:rtl_Bootstrap → resolve their helper exe via -# getExecutablePath()→"../bin" (the dmake solver/bin layout), which flat -# Bazel staging cannot satisfy without source changes. The gtest_test rule -# has a `companions` hook ready for when that layout is reproduced. +# NOT YET WIRED: osl_Security only — it does not COMPILE (testshl2 header, see +# above), which is a different thing from an excluded-because-failing test. +# The child-process spawners (osl_process, rtl_Process, rtl_Bootstrap) used to +# be listed here; they are wired now via bin_layout (see above). test_suite( name = "sal_tests", tests = [ @@ -399,6 +465,9 @@ test_suite( ":tcwf", ":osl_old_test_file", ":osl_Thread", + ":osl_process", + ":rtl_Process", + ":rtl_Bootstrap", ":qa_rtl_strings", ":osl_SocketOld", ":osl_Socket_tests", diff --git a/main/sal/sal_qa.bzl b/main/sal/sal_qa.bzl index cc16b962dd..10fed987f7 100644 --- a/main/sal/sal_qa.bzl +++ b/main/sal/sal_qa.bzl @@ -10,6 +10,7 @@ only. They differ just in their source(s) and a per-dir include for a local * sal_headers + stlport + boost; sal_implib link; sal3.dll at run time. """ +load("@rules_cc//cc:defs.bzl", "cc_binary") load("//build/rules:gtest_test.bzl", "gtest_test") _DEFINES = [ @@ -20,26 +21,50 @@ _DEFINES = [ "snprintf=_snprintf", ] +_COMMON_COPTS = [ + "/Imain/sal/inc/pch", + "/Imain/sal/qa/inc", + "/Imain/soltools/winunistd", + "/Zc:wchar_t-", +] + +_COMMON_DEPS = [ + "//main/sal:sal_headers", + "//main/stlport:stlport", + "@boost.legacy//:boost.legacy", +] + def sal_qa_test(name, srcs, subdir, copts = [], deps = [], linkopts = [], companions = [], **kwargs): gtest_test( name = name, srcs = srcs, companions = companions, - copts = [ - "/Imain/sal/inc/pch", - "/Imain/sal/qa/inc", - "/Imain/sal/qa/" + subdir, - "/Imain/soltools/winunistd", - "/Zc:wchar_t-", - ] + copts, + copts = _COMMON_COPTS + ["/Imain/sal/qa/" + subdir] + copts, defines = _DEFINES, - deps = [ - "//main/sal:sal_headers", - "//main/stlport:stlport", - "@boost.legacy//:boost.legacy", - ] + deps, + deps = _COMMON_DEPS + deps, additional_linker_inputs = ["//main/sal:sal_implib"], linkopts = ["$(execpath //main/sal:sal_implib)"] + linkopts, runtime_dlls = ["//main/sal:sal3"], **kwargs ) + +def sal_qa_helper_exe(name, srcs, subdir, copts = [], deps = [], linkopts = []): + """A child process a sal/qa suite spawns by name (osl_process_child, …). + + Same compile environment as sal_qa_test, minus gtest: these are not test + harnesses but plain exes that the parent launches and then inspects the exit + code of, the arguments echoed by, or a dumped environment file written by. + Pass them to sal_qa_test's `companions` so they are staged beside the exe. + """ + cc_binary( + name = name, + srcs = srcs, + copts = _COMMON_COPTS + ["/Imain/sal/qa/" + subdir] + copts, + defines = _DEFINES, + deps = _COMMON_DEPS + deps, + additional_linker_inputs = ["//main/sal:sal_implib"], + # /MANIFEST:NO — the staging rule supplies an external <exe>.manifest + # for the VC90 CRT, exactly as it does for the test exe itself. + linkopts = ["$(execpath //main/sal:sal_implib)", "/MANIFEST:NO"] + linkopts, + testonly = True, + ) diff --git a/main/test/BUILD.bazel b/main/test/BUILD.bazel index 0eb2079979..a91f345a8e 100644 --- a/main/test/BUILD.bazel +++ b/main/test/BUILD.bazel @@ -1,6 +1,7 @@ package(default_visibility = ["//visibility:public"]) load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("//build/rules:gtest_test.bzl", "gtest_test") # main/test — libtest, AOO's C++ unit-test support library (test.dll). # @@ -87,3 +88,80 @@ filegroup( output_group = "interface_library", visibility = ["//visibility:public"], ) + +# ── smoke test for the OfficeConnection fixture itself ──────────────── +# MIGRATION-AUTHORED (qa/test_officeconnection.cxx) — see the long comment at +# the top of that file. Upstream's only C++ user of test::OfficeConnection is +# xmlsecurity/qa/certext, which cannot build here (it needs neon, which AOO +# replaced with curl and removed from the tree), so without this the fixture +# would ship unexercised. +# +# office_connection = True is what makes this fixture (b) rather than (a): the +# launcher creates a throwaway user installation and passes -env:arg-soffice / +# -env:arg-user, which test::OfficeConnection reads through rtl::Bootstrap. +# It LAUNCHES A FULL OFFICE, so it is slow by construction (14-18s, and always a +# cold start — the user installation is recreated per run). +# +# EXPECTED: `bazel test` warns that the execution time is "outside of range for +# MODERATE tests. Consider setting timeout=short or size=small". Do NOT take +# that advice. size=small caps the timeout at 60s, barely 3x the observed time +# on an idle machine (18.1s of that range was measured under parallel load), and +# the failure mode here is an UNBOUNDED retry loop in +# OfficeConnection::setUp() — the timeout is the only thing that ends a run +# where the office never accepts. The warning is the cost of that headroom. +gtest_test( + name = "test_qa_officeconnection", + srcs = ["qa/test_officeconnection.cxx"], + copts = [ + "/Imain/test/inc", + "/Imain/test/inc/pch", + "/Zc:wchar_t-", + ], + # NOT OOO_DLLIMPLEMENTATION_TEST — that is for building test.dll itself; + # a consumer wants the dllimport side of OOO_DLLPUBLIC_TEST. + defines = [ + "WNT", + "GUI", + "WIN32", + "snprintf=_snprintf", + ], + deps = [ + ":test_headers", + "//main/sal:sal_headers", + "//main/cppu:cppu_headers", + "//main/cppuhelper:cppuhelper_headers", + "//main/udkapi:udkapi_idl_headers", + "//main/offapi:offapi_idl_headers", + "//main/stlport:stlport", + "@boost.legacy//:boost.legacy", + ], + additional_linker_inputs = [ + "//main/sal:sal_implib", + "//main/cppu:cppu3_implib", + "//main/cppuhelper:cppuhelper_implib", + ":test_implib", + ], + linkopts = [ + "$(execpath //main/sal:sal_implib)", + "$(execpath //main/cppu:cppu3_implib)", + "$(execpath //main/cppuhelper:cppuhelper_implib)", + "$(execpath :test_implib)", + ], + # ONLY test.dll. It is a test-only library, so unlike everything else here + # it is genuinely not part of the staged install. + # + # Do NOT add sal3/cppu3/cppuhelper here to "be safe". With uno_install the + # usual advice inverts: co-locating a core UNO DLL BREAKS the bootstrap. + # cppuhelper resolves the directory it loads bootstrap.uno.dll and friends + # from via get_this_libpath() — Module::getUrlFromAddress() on ITSELF + # (cppuhelper/source/bootstrap.cxx) — and the loader searches the exe's own + # directory before PATH. A co-located cppuhelper3MSC.dll therefore makes + # that path the TEST staging dir instead of program/, and the bootstrap dies + # with "loading component library failed: .../<test>.run/bootstrap.uno.dll". + # Everything in the office closure is already on PATH via program/; leave it + # there. //main/svl:svl_qa_test_URIHelper passes for the same reason — it + # lists no runtime_dlls at all. + runtime_dlls = [":test"], + uno_install = "//main/staging:install", + office_connection = True, +) diff --git a/main/test/qa/test_officeconnection.cxx b/main/test/qa/test_officeconnection.cxx new file mode 100644 index 0000000000..4867221e4f --- /dev/null +++ b/main/test/qa/test_officeconnection.cxx @@ -0,0 +1,141 @@ +/************************************************************** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + *************************************************************/ + +// MIGRATION-AUTHORED, not an upstream test. +// +// test::OfficeConnection (libtest) is the "running office" test fixture: it +// launches a real soffice with -accept=pipe,name=...;urp and resolves a remote +// component context over URP. Upstream's ONLY C++ consumer of it is +// xmlsecurity/qa/certext, which cannot be built here at all — it includes +// <neon/ne_ssl.h> and calls ne_ssl_cert_read(), but AOO replaced neon with curl +// (main/ucb/source/ucp/webdav is curl-based, configure.in has no neon option and +// no neon source is in the tree; only a stale NEON3RDLIB lingers in +// solenv/inc/libs.mk). +// +// So without this file the fixture would be unexercised infrastructure — the +// kind that rots silently and is discovered to be broken by whoever first needs +// it. This exercises the whole round trip end to end: launch, URP resolve, +// remote service creation, clean termination. Same precedent as +// //build/testsupport:sal_process_init.cxx. + +#include "sal/config.h" + +#include "com/sun/star/lang/XMultiComponentFactory.hpp" +#include "com/sun/star/uno/Exception.hpp" +#include "com/sun/star/uno/Reference.hxx" +#include "com/sun/star/uno/XComponentContext.hpp" +#include "com/sun/star/uno/XInterface.hpp" +#include "rtl/bootstrap.hxx" +#include "rtl/string.hxx" +#include "rtl/ustring.hxx" + +#include <preextstl.h> +#include "gtest/gtest.h" +#include <postextstl.h> + +#include "test/officeconnection.hxx" + +namespace { + +namespace css = com::sun::star; + +// One office for the whole test case, not one per TEST_F: setUp() launches a +// process and tearDown() terminates it, so a per-test fixture would boot and +// shut down a full soffice for every assertion. +class OfficeConnectionTest: public ::testing::Test { +protected: + static test::OfficeConnection * connection_; + + // OfficeConnection::setUp() can throw a UNO exception (toAbsoluteFileUrl + // raises RuntimeException; the resolve loop only swallows + // NoConnectException). UNO exceptions do NOT derive from std::exception, + // so gtest reports them as a bare "Unknown C++ exception" with no message — + // useless for diagnosis. Catch and surface the text, plus the two + // arguments the fixture ran with, since a wrong arg-soffice/arg-user is by + // far the most likely cause. + static void SetUpTestCase() { + connection_ = new test::OfficeConnection; + try { + connection_->setUp(); + } catch (const css::uno::Exception & e) { + FAIL() << "OfficeConnection::setUp() threw " + << rtl::OUStringToOString( + e.Message, RTL_TEXTENCODING_UTF8).getStr() + << " [arg-soffice=" << reportArgument("soffice") + << "] [arg-user=" << reportArgument("user") << "]"; + } + } + + static rtl::OString reportArgument(char const * name) { + rtl::OUString value; + if (!rtl::Bootstrap::get( + rtl::OUString::createFromAscii("arg-") + + rtl::OUString::createFromAscii(name), + value)) + { + return rtl::OString(RTL_CONSTASCII_STRINGPARAM("<unset>")); + } + return rtl::OUStringToOString(value, RTL_TEXTENCODING_UTF8); + } + + static void TearDownTestCase() { + connection_->tearDown(); + delete connection_; + connection_ = 0; + } +}; + +test::OfficeConnection * OfficeConnectionTest::connection_ = 0; + +// The URP resolve succeeded and handed back a usable remote context. +TEST_F(OfficeConnectionTest, resolvesComponentContext) { + ASSERT_TRUE(connection_ != 0); + ASSERT_TRUE(connection_->getComponentContext().is()); +} + +// The context is genuinely the OFFICE's, not a local one: ask it for its +// service manager and instantiate a service that only a running office +// provides. This is what distinguishes fixture (b) from the in-process +// bootstrap of fixture (a). +TEST_F(OfficeConnectionTest, createsRemoteService) { + css::uno::Reference< css::uno::XComponentContext > context( + connection_->getComponentContext()); + ASSERT_TRUE(context.is()); + + css::uno::Reference< css::lang::XMultiComponentFactory > manager( + context->getServiceManager()); + ASSERT_TRUE(manager.is()); + + css::uno::Reference< css::uno::XInterface > desktop( + manager->createInstanceWithContext( + rtl::OUString( + RTL_CONSTASCII_USTRINGPARAM("com.sun.star.frame.Desktop")), + context)); + ASSERT_TRUE(desktop.is()); +} + +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/main/test/readme.md b/main/test/readme.md index 92ff1fda42..1f34fd0682 100644 --- a/main/test/readme.md +++ b/main/test/readme.md @@ -57,6 +57,8 @@ holdouts). This brings the test layer onto Bazel so suites run under - `//main/desktop:desktop_qa_dp_version` — extension version ordering - `//main/sal:osl_Socket_tests`, `:osl_StreamSocket`, `:osl_DatagramSocket`, `:osl_AcceptorSocket` (4 of the 8 socket suites; see the socket note below) + - `//main/sal:rtl_Process` (3), `:osl_process` (7/8), `:rtl_Bootstrap` + (25/30) — the child-process suites; see "the `bin` layout" below **Tests with private IDL types** (cppu/qa has a `types.idl` defining Enum1/Struct1/Interface1/… used only by the tests): reuse the `idl_library` @@ -102,10 +104,39 @@ holdouts). This brings the test layer onto Bazel so suites run under b. **Running-office connection** — `test::OfficeConnection` (libtest) starts a real soffice with `-accept=pipe,name=…;urp` and resolves a remote - context over URP. **Still unwired.** `test.dll` is built and its arguments - come from `rtl::Bootstrap` (`arg-soffice=path:<soffice.exe>`, - `arg-user=<user installation>`), so what is missing is the process - lifecycle, not the plumbing. + context over URP. Pass `office_connection = True` **together with** + `uno_install` (the fixture still bootstraps an in-process context to build + the URL resolver). The launcher then creates a throwaway user installation + and passes the two arguments the fixture reads through `rtl::Bootstrap`: + `-env:arg-soffice=path:<soffice.exe>` and `-env:arg-user=<dir>`. + + **There is no dmake recipe to port for this.** solenv's C++ run rule + (`_tg_app.mk`, `APP1TEST`) invokes the bare exe with nothing but + `--gtest_output=`, so it never sets these at all — an upstream gap, not + something the migration dropped. The complete recipe is the **Java** one, + `installationtest.mk::javatest`, and the launcher mirrors it (including + wiping the user installation before *and* after). One deliberate + divergence: Java passes `arg-user` as a `file://` URL, but the C++ side + feeds it to `toAbsoluteFileUrl()` → + `osl::FileBase::getFileURLFromSystemPath()`, so it must be a **native + path**. + + **Upstream's only C++ consumer cannot be built**: + `xmlsecurity/qa/certext` includes `<neon/ne_ssl.h>` and calls + `ne_ssl_cert_read()`, but AOO replaced neon with **curl** — WebDAV + (`ucb/source/ucp/webdav`) is curl-based, `configure.in` has no neon + option, and no neon source exists in the tree (only a stale `NEON3RDLIB` + in `solenv/inc/libs.mk`). That is a dead dependency, not a migration gap, + and it is why the fixture is exercised by a migration-authored smoke test + (`//main/test:test_qa_officeconnection`) instead — otherwise it would ship + unexercised and rot. + + Note `OfficeConnection::setUp()` retries the resolve in an **unbounded** + loop, so a soffice that never accepts hangs until the Bazel test timeout. + That is why `office_connection` defaults `size` to `large`. + + Green: `//main/test:test_qa_officeconnection` (~13 s — a full office boot, + URP resolve, remote service creation and clean terminate). ## Gotchas (learned the hard way) @@ -176,6 +207,52 @@ holdouts). This brings the test layer onto Bazel so suites run under [gtest_test.bzl](../../build/rules/gtest_test.bzl) does this, and it is why `uno_install` works from any package depth. +- **A backslash is an ESCAPE character in an `rtl::Bootstrap` value.** Every + value `Bootstrap::get()` returns goes through macro expansion, where `read()` + ([sal/rtl/source/bootstrap.cxx](../sal/rtl/source/bootstrap.cxx)) turns `\X` + into `X` and `\uXXXX` into a code point. So a raw Windows path handed to a + bootstrap variable comes back with **every separator gone** — + `C:\Users\x` → `C:Usersx` — and `getFileURLFromSystemPath` then fails with + 21. Double the backslashes so one survives each unescape (the `gtest_test` + launcher does this with cmd's `%VAR:\=\\%`). The dmake/Java side never hit + this because it passes a `file://` URL, which has no backslashes. + +- **With `uno_install`, co-locating a core UNO DLL BREAKS the bootstrap.** This + inverts the usual "list every transitive DLL in `runtime_dlls`" advice, so it + is easy to walk into while trying to be careful. cppuhelper finds the + directory to load `bootstrap.uno.dll` (and the other stoc bootstrap + components) from via `get_this_libpath()` — `Module::getUrlFromAddress()` on + *itself*, in [cppuhelper/source/bootstrap.cxx](../cppuhelper/source/bootstrap.cxx). + The loader searches the exe's own directory before `PATH`, so a co-located + `cppuhelper3MSC.dll` makes that path the *test staging dir* and the bootstrap + dies with `loading component library failed: …/<test>.run/bootstrap.uno.dll`. + Everything in the office closure is already reachable through `program/` on + `PATH` — leave it there. Both `//main/svl:svl_qa_test_URIHelper` and + `//main/test:test_qa_officeconnection` list nothing but their own test-only + DLL for exactly this reason. + + (`bootstrap.uno.dll` is also one of the hardcoded-name cases CLAUDE.md warns + about for the `.uno` infix divergence — but it is staged under the upstream + name, so the infix is *not* what bites here. The directory is.) + +- **"`../bin`" was a misreading — it is the identity.** The child-process + suites were long recorded as unwirable without source changes because they + "resolve their helper exe via `getExecutablePath()`+`/../bin`". They don't. + The idiom (`osl_process.cxx::getExecutablePath`, `rtl_Process.cxx` / + `rtl_Bootstrap.cxx::getModulePath`) is + + ``` + dir-of(own module) → strip the last path component → append "bin" + ``` + + and under dmake both parent and child lived in `solver/bin`, so that + round-trip resolves back to the *same* directory. Naming the staging dir + `bin` reproduces it exactly — that is all `gtest_test`'s `bin_layout` does + (it also implies `run_in_staged_dir`). No source change, no `companions` + layout gymnastics. Worth remembering as a class: a path expression that looks + like it reaches out of the staging dir may just be a no-op round-trip that + the old layout satisfied trivially. + - **Staging a data file beside the exe is not enough.** Co-located DLLs resolve regardless (the loader searches the exe's own path), which makes it easy to assume relative file opens will too — they don't. A test that opens a fixture by bare relative name @@ -203,11 +280,11 @@ holdouts). This brings the test layer onto Bazel so suites run under ## The sal suite is deliberately NOT a green gate -`//main/sal:sal_tests` runs **every** migrated self-contained sal/qa test — 45 +`//main/sal:sal_tests` runs **every** migrated self-contained sal/qa test — 48 targets, passing and failing alike, on the principle that failures are information, not something to hide (the rationale lives next to the `test_suite` in [main/sal/BUILD.bazel](../sal/BUILD.bazel)). So expect it to be -red. As of 2026-08-02, 34 pass and these 11 fail, each on its **own merits** — +red. As of 2026-08-04, 35 pass and these 13 fail, each on its **own merits** — none is a build or loader problem, and the source is out of scope: | Target | Failing | Why | @@ -219,6 +296,8 @@ none is a build or loader problem, and the source is out of scope: | `osl_Thread` | 1 | `resume_001` is a timing race; flaky, not deterministic | | `rtl_OUString2` | 1 | `convertFromString` expects `\x80` to fail UTF-8 validation — test-data drift, same class as `rtl_textcvt` | | `qa_rtl_strings` | 1 | `Convert.convertToString` — same text-conversion drift | +| `osl_process` | 1 | `osl_execProc_merged_child_environment` asserts the merged vars come back in the order the test listed them (`std::equal` over a vector), but Windows returns the environment block sorted case-insensitively — `PAT`, `Patha`, `PATHb` vs the expected `PAT`, `PATHb`, `Patha`. Ordering bug in the test | +| `rtl_Bootstrap` | 5 | 4 of them (`getFrom_004_1`, `setIniFilename_002`, `testOverride`, `testNonexisting`) read the **default** bootstrap context and expect `testshl2.ini` — the default ini is `<exename>.ini`, and under the retired testshl2 harness the process *was* `testshl2.exe` (the log shows ours resolving `rtl_Bootstrap.ini`). `expandMacrosFrom_002_2` is ini cross-reference precedence | | `osl_SocketOld` | 10 | see socket note below | | `osl_SocketAddr` | 3 | see socket note below | | `osl_Socket2` | 7 | see socket note below | @@ -283,8 +362,25 @@ already wired (`rtl_OString2`, `rtl_str`, `rtl_string`, `rtl_OUString2`, `rtl_ustr`). So it is **dead weight, not a blocker**; there is nothing to gain by migrating it. -Still unwired: child-process tests (`osl/process`, `rtl/bootstrap`, -`rtl/process`) → they resolve their helper exe via `getExecutablePath()`+`"/../bin"` -(the dmake `solver/bin` layout), which flat Bazel staging can't satisfy without -source changes — `gtest_test` has a `companions` hook ready for when that layout -is reproduced. +The child-process tests (`osl/process`, `rtl/process`, `rtl/bootstrap`) were +listed here as unwired for exactly that reason; they are wired now via +`bin_layout` + `companions` (see the gotcha above). Their helper exes are built +by `sal_qa_helper_exe` in [sal_qa.bzl](../sal/sal_qa.bzl) — the same compile +environment as `sal_qa_test` minus gtest. + +Both TUs of the `osl/process` pair walk the environment block with `LPTSTR` / +`GetEnvironmentStrings()` / `_tcslen()` but **neither includes `<windows.h>`**, +and nothing they include reaches it (`precompiled_sal.hxx` is empty; no rtl +header pulls it in). That is bit-rot, not a migration gap — dmake gates every +`qa/` dir behind `ENABLE_UNIT_TESTS`, which defaults to NO, so these had not +compiled in years. Supplied with `/FIwindows.h`, but the two want **opposite** +flavours and getting it backwards compiles cleanly while silently walking an +ANSI block as wide chars: the parent defines neither `UNICODE` nor `_UNICODE` +and does `std::string(p)` → ANSI; the child defines both itself and does +`reinterpret_cast<wchar_t*>` → needs `UNICODE` defined *before* `windows.h` +arrives, hence `/DUNICODE=` (empty, so the source's own `#define UNICODE` is an +identical redefinition rather than a C4005). `/DNOMINMAX` is already global in +the toolchain, so force-including `windows.h` ahead of `<algorithm>` is safe. + +Still unwired: nothing in sal/qa except `osl_Security`, which does not compile +(testshl2 header — see the gotcha above).
