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 fc3b72efc8eeab318efe05634af22b9823599d4e
Author: Peter Kovacs <[email protected]>
AuthorDate: Wed Aug 5 07:28:32 2026 +0200

    test(configmgr): fixture (c) — own mini installation; qa/unit wired RED
    
    Four new gtest_test capabilities, all general, and the finding that the test
    which drove them cannot be made green from the build system.
    
    FIXTURE (c) — "own mini installation": the test supplies its own registry
    data and builds its own service manager, with no default bootstrap and no
    soffice.  Distinct from (a) in-process bootstrap and (b) OfficeConnection.
      * data_tree={label:"rel/path"} — staging at a CHOSEN path rather than flat
        by basename.  Needed when the SHAPE of the tree is the fixture: $ORIGIN
        resolves against an ini's own directory, and a mini installation has two
        files named bootstrap.ini.
      * ure_bootstrap="rel/path" — point URE_BOOTSTRAP at a staged ini instead 
of
        the install's program/fundamental.ini.  Still requires uno_install: only
        the DATA root moves, the UNO DLL closure stays on PATH from program/.
      * env={} / prerun=[] — extra environment, and launcher command lines run
        before the exe (each || exit /b 1, so a fixture that fails to build 
fails
        the test loudly).  Both expand $(RUNDIR)/$(PROGRAM)/$(SCRATCH)/
        $(SCRATCH_URL).  prerun exists for fixtures only buildable AT RUN TIME: 
a
        registry a UNO tool must write would, as a build action, need its own
        staged DLL closure and CRT manifest, and the component location it 
records
        is an ABSOLUTE path that has no business in a cacheable artifact.
    
    $(SCRATCH) is a fresh, writable, per-run dir under TEST_TMPDIR, wiped before
    AND after.  Everything a test writes belongs there — the staged dir is
    bazel-out, i.e. build output.  office_connection's user installation is now
    this same mechanism (test_qa_officeconnection still 2/2, ~13s).
    
    BACKSLASH RULE, RESTATED BECAUSE IT CUTS BOTH WAYS: an env value the test
    reads back through rtl::Bootstrap::get() is macro-expanded, so its
    backslashes must be DOUBLED (arg-user); one read with plain getenv() must 
NOT
    be (CONFIGMGR_UNIT_FORWARD_STRING).  Prefer a file:/// URL where the 
consumer
    takes one — forward slashes sidestep the question.
    
    configmgr/qa/unit is WIRED AND RED, and cannot go green without a source
    change.  It builds and links, then dies in prerun with
    "configmgr.uno.dll: cannot get symbol: component_writeInfo".
    
    The blocker recorded on the frontier for this test — "needs a custom
    ure_bootstrap root + extra env" — was read off qa/unit/makefile.mk and was
    WRONG.  That fixture is built here and works; the mini installation stages 
and
    resolves.  The real wall is that the test's bootstrap PREMISE was retired
    under it: regcomp -register registers a component by calling
    component_writeInfo(), the pre-.component mechanism, and AOO dropped it.
    configmgr/source/services.cxx exports only component_getFactory and
    component_getImplementationEnvironment; tree-wide there are 438 .component
    files against a handful of component_writeInfo exporters, all odk examples,
    workbench samples and legacy tests.
    
    The obvious escape is closed too: test.cxx calls the ONE-ARGUMENT
    createRegistryServiceFactory overload, which defaults bReadOnly to 
sal_False,
    so SimpleRegistry::open() receives (readonly=false, create=true) — and the
    textual (XML) registry branch is reachable only when bReadOnly && !bCreate.
    UNO_SERVICES does not help: it belongs to
    defaultBootstrap_InitialComponentContext, not that deprecated path.
    
    Upstream knows.  The commit that moved this suite to GoogleTest (7231f715d2,
    "#i125003#") says in its own message: "All tests fail and on Windows it
    doesn't start running, but these look like problems from before and at least
    it doesn't run during the build."  dmake gates every qa/ dir behind
    ENABLE_UNIT_TESTS=NO, so nobody has run it since — the same bit-rot as the
    missing <windows.h> in sal's osl/process suites, just fatal instead of
    cosmetic.
    
    Kept wired because it BUILDS, per the policy that a buildable test gets 
wired
    even when red on its own merits (cf. writerfilter_qa_doctok, red on a 
fixture
    never checked in).  The regcomp line is deliberately left in place: its 
error
    names the retired mechanism exactly, which beats a vaguer failure later 
inside
    Test::SetUp.  Fixing it means a source change — port test.cxx to
    defaultBootstrap_InitialComponentContext + a services.rdb (i.e. make it
    fixture (a), already supported), pass sal_True for bReadOnly with a textual
    registry, or re-export component_writeInfo (reviving what AOO retired).
    
    Also corrects the frontier's "3/3" for test_qa_officeconnection: it is 2 
cases.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 CLAUDE.md                  |  63 +++++++++++++--
 build/rules/gtest_test.bzl | 188 +++++++++++++++++++++++++++++++++++++++-----
 main/configmgr/BUILD.bazel | 189 +++++++++++++++++++++++++++++++++++++++++++++
 main/configmgr/readme.md   | 120 ++++++++++++++++++++++++++++
 main/test/readme.md        |  67 +++++++++++++++-
 5 files changed, 601 insertions(+), 26 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index ce1d4525b7..938985834d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -84,7 +84,7 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                    (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
+                   //main/test:test_qa_officeconnection (2/2, ~13s — 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
@@ -135,15 +135,64 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                    enumerating dozens of transitive DLLs for sw/sfx-sized 
libraries.
                    writerfilter_qa_doctok wired too (RED on its own missing 
fixture:
                    reads <cwd>/test.doc, never checked in; testInitUno passes).
+                   FIXTURE (c) DONE 2026-08-05 — "own mini installation": the 
test
+                   supplies its own registry data and builds its own service 
manager,
+                   no default bootstrap and no soffice.  Four NEW general 
gtest_test
+                   attrs, all reusable: data_tree={label:"rel/path"} (staging 
at a
+                   CHOSEN path, not flat by basename — $ORIGIN resolves 
against an
+                   ini's own dir and a mini installation has TWO files named
+                   bootstrap.ini), ure_bootstrap="rel/path" (point 
URE_BOOTSTRAP at a
+                   staged ini instead of the install's 
program/fundamental.ini; STILL
+                   needs uno_install — only the DATA root moves, the DLL 
closure stays
+                   on PATH from program/), env={} and prerun=[] (extra env 
vars, and
+                   launcher command lines run before the exe, each || exit /b 
1).  Both
+                   expand $(RUNDIR)/$(PROGRAM)/$(SCRATCH)/$(SCRATCH_URL).  
$(SCRATCH) is
+                   a fresh WRITABLE dir under TEST_TMPDIR wiped before AND 
after —
+                   everything a test writes goes there, since the staged dir is
+                   bazel-out and must be read-only; office_connection's user
+                   installation is now this same mechanism.  prerun exists for 
fixtures
+                   only buildable AT RUN TIME (a registry a UNO tool must 
write: as a
+                   build action it needs its own DLL closure + CRT manifest, 
and the
+                   location it records is an ABSOLUTE path that must not enter 
a cached
+                   artifact).  BACKSLASH RULE CUTS BOTH WAYS: an env value 
read back via
+                   rtl::Bootstrap::get() is macro-expanded so must be DOUBLED
+                   (arg-user); one read with plain getenv() must NOT be
+                   (CONFIGMGR_UNIT_FORWARD_STRING).  Prefer a file:/// URL 
where the
+                   consumer takes one.
                    STILL TODO, with what each actually needs:
-                    • configmgr/qa/unit — its OWN mini installation 
(urebootstrap.ini +
-                      data.xcd + 2 generated inis) plus unit.rdb = types.rdb 
with
-                      configmgr.uno registered via //main/cpputools:regcomp 
(that tool
-                      IS built), and env 
CONFIGMGR_UNIT_FORWARD_STRING=<unit.rdb>.
-                      Needs gtest_test to accept a CUSTOM ure_bootstrap root + 
extra
-                      env, not just uno_install=//main/staging:install.
+                    • configmgr/qa/unit — WIRED 2026-08-05 but RED, and NOT 
fixable
+                      without a SOURCE change.  The blocker recorded here 
before ("needs
+                      a custom ure_bootstrap root + extra env") was read off
+                      qa/unit/makefile.mk and was WRONG: that fixture is built 
and works
+                      (mini installation stages + resolves).  The real wall is 
that the
+                      test's BOOTSTRAP PREMISE was retired under it — regcomp 
-register
+                      registers via component_writeInfo(), the pre-.component 
mechanism,
+                      and configmgr/source/services.cxx exports only
+                      component_getFactory + 
component_getImplementationEnvironment
+                      (tree-wide: 438 .component files vs a handful of
+                      component_writeInfo exporters, all odk 
examples/workbench/legacy
+                      tests) ⇒ "cannot get symbol: component_writeInfo".  The 
escape of
+                      feeding it a TEXTUAL (XML) services registry is closed: 
test.cxx
+                      calls the 1-ARG createRegistryServiceFactory overload, 
which
+                      defaults bReadOnly=sal_False, so SimpleRegistry::open() 
gets
+                      (readonly=false, create=true) and the textual branch 
needs
+                      bReadOnly && !bCreate.  UNO_SERVICES belongs to
+                      defaultBootstrap_InitialComponentContext, not this path. 
 UPSTREAM
+                      KNOWS: the GoogleTest-migration commit 7231f715d2 
(#i125003#) says
+                      "All tests fail and on Windows it doesn't start 
running".  Kept
+                      wired (it BUILDS) with the regcomp line deliberately 
left in — its
+                      error names the retired mechanism exactly.  Fix = port 
test.cxx to
+                      defaultBootstrap_InitialComponentContext + services.rdb 
(i.e.
+                      fixture (a), already supported), or pass 
bReadOnly=sal_True with a
+                      textual registry, or re-export component_writeInfo 
(worst).
+                      GENERAL LESSON: a qa/ dir gated behind 
ENABLE_UNIT_TESTS=NO for a
+                      decade may encode a mechanism the product no longer has 
— check
+                      the test's BOOTSTRAP PATH against current source before 
costing
+                      the fixture.
                     • cppuhelper/qa/propertysetmixin — UNO component DLL + own
                       types.idl + a per-test services.rdb 
(packcomponents.xslt).
+                      Uses the MODERN .component mechanism, so it should not 
hit the
+                      configmgr wall; fixture (c)'s data_tree/env/prerun 
should cover it.
                     • 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
diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl
index 9b100ec977..2ed6785146 100644
--- a/build/rules/gtest_test.bzl
+++ b/build/rules/gtest_test.bzl
@@ -59,6 +59,47 @@ def _windows_relpath(from_dir, to_dir):
         common = i + 1
     return "..\\" * (len(f) - common) + "\\".join(t[common:])
 
+# Tokens usable in `env` values and `prerun` command lines.  They stand for
+# paths only knowable at run time, and expand to launcher variables:
+#   $(RUNDIR)      the staged directory (native, no trailing separator)
+#   $(PROGRAM)     the staged install's program/ dir (needs uno_install)
+#   $(SCRATCH)     a fresh, empty, WRITABLE per-run directory (native)
+#   $(SCRATCH_URL) the same directory as forward slashes, for a file:/// URL
+# Everything a test writes belongs under $(SCRATCH): the staged dir lives in
+# bazel-out and must be treated as read-only build output.
+_TOKENS = [
+    # $(SCRATCH_URL) FIRST — otherwise $(SCRATCH) matches its prefix.
+    ("$(SCRATCH_URL)", "%_SCRATCHU%"),
+    ("$(SCRATCH)", "%_SCRATCH%"),
+    ("$(RUNDIR)", "%_RUN%"),
+    ("$(PROGRAM)", "%_PROG%"),
+]
+
+def _expand_tokens(s):
+    for token, var in _TOKENS:
+        s = s.replace(token, var)
+    return s
+
+def _uses_token(strings, token):
+    for s in strings:
+        if s.find(token) != -1:
+            return True
+    return False
+
+def _env_lines(env):
+    # sorted() so the launcher is byte-identical across analyses (a dict's
+    # iteration order is stable in Starlark, but the attr comes from a BUILD 
file
+    # where the author's ordering carries no meaning).
+    return ['set "%s=%s"' % (k, _expand_tokens(env[k])) for k in sorted(env)]
+
+def _prerun_lines(prerun):
+    # `|| exit /b 1` on every one: a fixture that failed to build must fail the
+    # test loudly, not leave the suite to report a confusing downstream error.
+    out = []
+    for cmd in prerun:
+        out.append(_expand_tokens(cmd) + " || exit /b 1")
+    return out
+
 def _staged_gtest_test_impl(ctx):
     # The staging dir is normally "<name>.run".  bin_layout makes it
     # "<name>.run/bin" instead — the ONE thing the child-process suites need.
@@ -110,6 +151,20 @@ def _staged_gtest_test_impl(ctx):
     ctx.actions.symlink(output = man, target_file = ctx.file.app_manifest)
     staged.append(man)
 
+    # data_tree — fixture files staged at an EXPLICIT relative path rather than
+    # flat by basename (which is all `runtime` can express).  Needed whenever 
the
+    # shape of the tree is what is under test: a mini UNO installation resolves
+    # $ORIGIN against the ini's own directory, so basis/program/uno.ini has to 
be
+    # exactly there, and two files in it are both named "bootstrap.ini".
+    for label, rel in ctx.attr.data_tree.items():
+        files = label[DefaultInfo].files.to_list()
+        if len(files) != 1:
+            fail("data_tree entry %s must provide exactly one file, got %d" %
+                 (label.label, len(files)))
+        o = ctx.actions.declare_file(d + "/" + rel)
+        ctx.actions.symlink(output = o, target_file = files[0])
+        staged.append(o)
+
     # ── UNO environment (subsequent / in-process-bootstrap tests) ────────────
     # A test that calls cppu::defaultBootstrap_InitialComponentContext() needs 
a
     # real UNO installation: type + service rdbs, and every component DLL named
@@ -158,18 +213,82 @@ def _staged_gtest_test_impl(ctx):
              "bootstraps an in-process context to build the URL resolver, and 
" +
              "the soffice it launches comes from the staged install.")
 
+    # A launcher .bat is needed for anything the bare exe cannot express 
itself:
+    # a working directory, an environment, or a fixture built at run time.
+    env = ctx.attr.env
+    prerun = ctx.attr.prerun
+    values = env.values() + prerun
+    need_scratch = (ctx.attr.office_connection or
+                    _uses_token(values, "$(SCRATCH"))
+    if _uses_token(values, "$(PROGRAM)") and not uno_program_dir:
+        fail("$(PROGRAM) needs uno_install — there is no staged install to 
point at.")
+    if ctx.attr.ure_bootstrap and not uno_program_dir:
+        fail("ure_bootstrap needs uno_install: it only REDIRECTS the bootstrap 
" +
+             "ini, it does not supply the UNO DLL closure the test still 
loads.")
+
     executable = staged_exe
-    if ctx.attr.run_in_staged_dir or uno_program_dir:
+    if (ctx.attr.run_in_staged_dir or uno_program_dir or env or prerun):
         launcher_dir = staged_exe.dirname  # the .bat sits beside the staged 
exe
         lines = ["@echo off", "setlocal"]
+
+        # %~dp0 keeps its trailing backslash, which does not concatenate 
cleanly
+        # and cannot be quoted next to one; "%~dp0." normalized by %%~fI gives 
the
+        # same directory without it.
+        lines += ['for %%I in ("%~dp0.") do set "_RUN=%%~fI"']
+
+        if need_scratch:
+            # Everything the test writes goes here.  Wiped BEFORE as well as
+            # after: a run that dies without cleaning up must not hand its 
state
+            # to the next one (a stale user installation is exactly the kind of
+            # thing that makes a failure unreproducible).
+            lines += [
+                'set "_SCRATCH=%TEST_TMPDIR%\\scratch"',
+                'if "%TEST_TMPDIR%"=="" set "_SCRATCH=%_RUN%\\scratch"',
+                # TEST_TMPDIR arrives with forward slashes; the native form is
+                # what osl's getFileURLFromSystemPath and cmd both want.
+                'set "_SCRATCH=%_SCRATCH:/=\\%"',
+                'if exist "%_SCRATCH%" rmdir /s /q "%_SCRATCH%"',
+                'mkdir "%_SCRATCH%" || exit /b 1',
+                # file:/// URL form: only the separators differ on Windows.
+                'set "_SCRATCHU=%_SCRATCH:\\=/%"',
+            ]
+
         if uno_program_dir:
             lines += [
                 'set "_EXE=%~dp0' + staged_exe.basename + '"',
                 # Resolved from the launcher's own location (%~dp0), not %CD%.
                 'for %%I in ("%~dp0' + _windows_relpath(launcher_dir, 
uno_program_dir) +
                 '") do set "_PROG=%%~fI"',
-                # vnd.sun.star.pathname: takes a native path, not a file URL.
-                'set 
"URE_BOOTSTRAP=vnd.sun.star.pathname:%_PROG%\\fundamental.ini"',
+            ]
+
+            # Which installation the UNO bootstrap describes.  Normally the
+            # staged office (program/fundamental.ini, whose ${ORIGIN} then
+            # supplies UNO_TYPES / UNO_SERVICES / URE_INTERNAL_LIB_DIR).  A 
test
+            # that is ABOUT the configuration layer needs its own tiny
+            # installation instead, so it can assert on data it controls rather
+            # than on whatever the real registry happens to hold — that is what
+            # ure_bootstrap redirects to.  The DLLs still come from program/ 
via
+            # PATH below; only the data root moves.
+            #
+            # NOTE single backslashes here, unlike arg-soffice/arg-user further
+            # down.  URE_BOOTSTRAP is consumed by the bootstrap machinery 
itself
+            # (rtl_bootstrap_args_open on the ini path) and is NOT put through
+            # macro expansion, so its separators survive.  Values that a test
+            # later reads back via rtl::Bootstrap::get() ARE expanded, and 
those
+            # must double their backslashes.
+            #
+            # vnd.sun.star.pathname: takes a native path, not a file URL.
+            if ctx.attr.ure_bootstrap:
+                lines += [
+                    'set "URE_BOOTSTRAP=vnd.sun.star.pathname:%_RUN%\\' +
+                    ctx.attr.ure_bootstrap.replace("/", "\\") + '"',
+                ]
+            else:
+                lines += [
+                    'set 
"URE_BOOTSTRAP=vnd.sun.star.pathname:%_PROG%\\fundamental.ini"',
+                ]
+
+            lines += [
                 # Component DLLs named in services.rdb are loaded at run time 
and
                 # live in program/; the exe's own directory still wins for 
what it
                 # imports directly, so its staged copies are unaffected.
@@ -213,14 +332,10 @@ def _staged_gtest_test_impl(ctx):
                 # //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().
+                # The user installation is just the general scratch directory —
+                # created and wiped by the block above, which is why there is 
no
+                # per-run bookkeeping here.
                 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
@@ -233,23 +348,25 @@ def _staged_gtest_test_impl(ctx):
                     # hit this: it passes a file:// URL, which has no
                     # backslashes.)
                     'set "arg-soffice=path:%_SOFFICE:\\=\\\\%"',
-                    'set "arg-user=%_USER:\\=\\\\%"',
+                    'set "arg-user=%_SCRATCH:\\=\\\\%"',
                 ]
-            lines += ['"%_EXE%" %*']
+            lines += _env_lines(env) + _prerun_lines(prerun) + ['"%_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
             # execroot, not the exe's directory (the loader finds the staged 
DLLs
             # via the exe's own path, which is why those work regardless).
-            lines += [
-                'cd /d "%~dp0" || exit /b 1',
-                '"%~dp0' + staged_exe.basename + '" %*',
-            ]
+            # Only when that was actually asked for — a launcher that exists
+            # solely to set `env` must not silently move the cwd as well.
+            if ctx.attr.run_in_staged_dir:
+                lines += ['cd /d "%~dp0" || exit /b 1']
+            lines += _env_lines(env) + _prerun_lines(prerun)
+            lines += ['"%~dp0' + staged_exe.basename + '" %*']
 
         # 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']
+        if need_scratch:
+            lines += ['rmdir /s /q "%_SCRATCH%" 2>nul']
         lines += ["exit /b %_RC%", ""]
 
         launcher = ctx.actions.declare_file(d + "/" + ctx.label.name + 
"_run.bat")
@@ -275,6 +392,10 @@ _staged_gtest_test = rule(
         "bin_layout": attr.bool(default = False),
         "office_connection": attr.bool(default = False),
         "uno_install": attr.label(allow_files = True),
+        "data_tree": attr.label_keyed_string_dict(allow_files = True),
+        "env": attr.string_dict(),
+        "prerun": attr.string_list(),
+        "ure_bootstrap": attr.string(),
     },
 )
 
@@ -297,6 +418,10 @@ def gtest_test(
         companions = [],
         bin_layout = False,
         office_connection = False,
+        data_tree = {},
+        env = {},
+        prerun = [],
+        ure_bootstrap = None,
         additional_linker_inputs = [],
         linkopts = [],
         size = None,
@@ -337,6 +462,29 @@ def gtest_test(
     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".
+
+    data_tree: {label: "relative/staged/path"} — like data_files, but staged at
+    a path you choose instead of flat by basename. Use it when the SHAPE of the
+    tree is the fixture: a mini UNO installation resolves $ORIGIN against the
+    ini's own directory, and two of its files are both named "bootstrap.ini".
+
+    env / prerun: extra environment variables, and command lines run in the
+    launcher just before the exe (each is `|| exit /b 1`-guarded). Both expand
+    $(RUNDIR) / $(PROGRAM) / $(SCRATCH) / $(SCRATCH_URL) — see _TOKENS.
+    prerun exists for fixtures that can only be BUILT at run time: a registry a
+    UNO tool has to write, for instance, where doing it as a build action would
+    mean bundling that tool with its own CRT manifest and DLL closure, and 
would
+    bake an absolute bazel-out path into a cached artifact.
+    NOTE an env value that the test reads back through rtl::Bootstrap::get()
+    goes through macro expansion, so its backslashes must be DOUBLED; one read
+    with plain getenv() must not be.
+
+    ure_bootstrap: staged-relative path (a data_tree entry) to a bootstrap ini
+    to use as URE_BOOTSTRAP instead of the install's program/fundamental.ini —
+    i.e. "run against THIS installation's data". For tests of the configuration
+    layer itself, which need a registry they control rather than the real one.
+    Requires uno_install: it moves the data root only, and the UNO DLL closure
+    still comes from program/ via PATH.
     """
     if size == None:
         size = "medium" if office_connection else "small"
@@ -369,5 +517,9 @@ def gtest_test(
         bin_layout = bin_layout,
         office_connection = office_connection,
         uno_install = uno_install,
+        data_tree = data_tree,
+        env = env,
+        prerun = prerun,
+        ure_bootstrap = ure_bootstrap or "",
         size = size,
     )
diff --git a/main/configmgr/BUILD.bazel b/main/configmgr/BUILD.bazel
index a2f120cc91..0fe5d132b8 100644
--- a/main/configmgr/BUILD.bazel
+++ b/main/configmgr/BUILD.bazel
@@ -1,6 +1,8 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("//build/rules:gtest_test.bzl", "gtest_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -87,3 +89,190 @@ cc_binary(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unit — the configuration layer against its OWN mini installation ──
+#
+# STATUS: WIRED AND RED, and it cannot go green without a SOURCE change (out of
+# scope).  It builds and links; it dies in the `prerun` fixture step with
+#     configmgr.uno.dll: cannot get symbol: component_writeInfo
+# That is not a migration defect.  `regcomp -register` registers a component by
+# calling component_writeInfo() on it — the PRE-.component registration
+# mechanism — and AOO retired that in the 3-layer-office removal: configmgr's
+# services.cxx exports only component_getFactory and
+# component_getImplementationEnvironment, and the tree now has 438 .component
+# files against a handful of component_writeInfo exporters (odk examples,
+# workbench samples, legacy tests).  qa/unit/makefile.mk was never updated.
+#
+# The obvious escape — hand it a textual (XML) services registry instead of a
+# binary one — is closed: test.cxx calls the ONE-argument
+# createRegistryServiceFactory overload, which defaults bReadOnly to sal_False,
+# so SimpleRegistry::open() gets (readonly=false, create=true), and the textual
+# branch is reachable only when bReadOnly && !bCreate (simpleregistry.cxx).
+# UNO_SERVICES does not help either: it belongs to
+# defaultBootstrap_InitialComponentContext, not to this deprecated path.
+#
+# Upstream knows.  The commit that moved this suite to GoogleTest (7231f715d2,
+# "#i125003#") says in its own message: "All tests fail and on Windows it 
doesn't
+# start running, but these look like problems from before and at least it 
doesn't
+# run during the build" — dmake gates every qa/ dir behind 
ENABLE_UNIT_TESTS=NO,
+# so nobody has run it since.  Same class of bit-rot as the missing <windows.h>
+# in sal's osl/process suites, just fatal instead of cosmetic.
+#
+# Kept wired anyway, per the policy that a test which BUILDS gets wired even 
when
+# it is red on its own merits (cf. //main/writerfilter:writerfilter_qa_doctok,
+# red on a fixture that was never checked in).  The regcomp line is 
deliberately
+# left in place: its error message names the retired mechanism exactly, which 
is
+# more useful than a vaguer failure later inside Test::SetUp.
+#
+# To fix it, one of these — all source changes:
+#   * port test.cxx to defaultBootstrap_InitialComponentContext + a 
services.rdb
+#     (i.e. make it fixture (a), which gtest_test already supports); or
+#   * pass sal_True for bReadOnly and feed it a textual services registry; or
+#   * re-export component_writeInfo from configmgr (reviving what AOO retired —
+#     the worst of the three).
+#
+# Everything BELOW this notice is verified working and is the reason the target
+# stays: the mini installation stages and resolves, and the four gtest_test
+# capabilities it drove (data_tree / ure_bootstrap / env / prerun) are general.
+#
+# This is a THIRD test fixture flavour, distinct from the two in
+# build/rules/gtest_test.bzl's notes.  It is not the in-process bootstrap of 
the
+# staged office (a), nor a launched soffice over URP (b): the test builds its 
own
+# service manager with cppu::createRegistryServiceFactory(unit.rdb) and points
+# the bootstrap at a four-file installation whose entire share/registry is one
+# data.xcd it ships.  That is the whole point — a test that asserts on
+# /org.openoffice.Setup Test/AString cannot run against the real registry, 
which
+# has no such key, and one that WRITES keys must not touch the real user 
profile.
+#
+# Mirrors the TEST target in qa/unit/makefile.mk, whose recipe is:
+#   * a mini installation under $(MISC)/unitdata (below);
+#   * unit.rdb = a copy of types.rdb with configmgr.uno registered into it via
+#     regcomp, passed to the test in CONFIGMGR_UNIT_FORWARD_STRING;
+#   * URE_BOOTSTRAP pointed at unitdata/urebootstrap.ini.
+#
+# Two deliberate divergences from that recipe, both forced:
+#
+# 1. unit.rdb is built by the LAUNCHER (gtest_test prerun), not by a build
+#    action.  regcomp bootstraps a UNO service manager, so as a build action it
+#    would need its own staged DLL closure and CRT manifest — and worse, the
+#    location it records for a component is an ABSOLUTE path
+#    (registercomponent.cxx::convertToFileUrl), which a cached artifact must 
not
+#    contain.  Built per run in the scratch dir, the path it bakes in is the
+#    staged install that this very run is using, and nothing goes stale.
+#    `-br types.rdb` (which dmake omits) keeps regcomp's OWN bootstrap off the
+#    default registry: with it, cppuhelper builds the service manager from the
+#    type registry plus its hardcoded bootstrap components, so regcomp never
+#    consults the URE_BOOTSTRAP we have just redirected at the mini 
installation.
+#
+# 2. bootstrap.ini is written to basis/program/ as well as brand/program/.
+#    The makefile writes it only under brand/, but AOO's Components::
+#    getModificationFileUrl() reads
+#    ${$OOO_BASE_DIR/program/bootstrap.ini:UserInstallation} — OOO_BASE_DIR, 
i.e.
+#    basis/ — so under the recipe as written the lookup yields nothing and 
every
+#    write test loses its changes.  The fixture is stale, not the code; since 
it
+#    is generated build data rather than source, it is fixed here.
+#    UserInstallation also cannot be the makefile's $ORIGIN/../../user: that
+#    resolves inside bazel-out, which is build output and must be treated as
+#    read-only.  It indirects through $CONFIGMGR_UNIT_USER instead, which the
+#    launcher points at the per-run scratch dir (writeModFile() does
+#    Directory::createPath, so the leaf need not exist).
+
+write_file(
+    name = "qa_unit_uno_ini",
+    out = "qa/unit/gen/uno.ini",
+    content = [
+        "[Bootstrap]",
+        "UNO_SHARED_PACKAGES_CACHE = $OOO_BASE_DIR",
+        "UNO_USER_PACKAGES_CACHE = 
${$OOO_BASE_DIR/program/bootstrap.ini:UserInstallation}",
+        "",
+    ],
+)
+
+# $CONFIGMGR_UNIT_USER is expanded by rtl::Bootstrap from the environment; the
+# launcher sets it to a file:/// URL under the scratch dir.  A URL, not a 
native
+# path, so there are no backslashes for bootstrap macro expansion to eat.
+#
+# TWO targets with identical content because data_tree is keyed by LABEL: one
+# label cannot be staged to two paths, and this file is needed at both
+# basis/program/ (what Components::getModificationFileUrl reads) and
+# brand/program/ (what the dmake recipe writes — kept for parity, in case
+# anything resolves it through BRAND_BASE_DIR).
+[
+    write_file(
+        name = "qa_unit_bootstrap_ini_" + where,
+        out = "qa/unit/gen/" + where + "/bootstrap.ini",
+        content = [
+            "[Bootstrap]",
+            "UserInstallation = $CONFIGMGR_UNIT_USER",
+            "",
+        ],
+    )
+    for where in [
+        "basis",
+        "brand",
+    ]
+]
+
+gtest_test(
+    name = "configmgr_qa_unit",
+    srcs = ["qa/unit/test.cxx"],
+    copts = [
+        "/Imain/configmgr/inc/pch",
+        "/Zc:wchar_t-",
+    ],
+    defines = [
+        "WNT",
+        "GUI",
+        "WIN32",
+        "snprintf=_snprintf",
+    ],
+    deps = [
+        "//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",
+    ],
+    linkopts = [
+        "$(execpath //main/sal:sal_implib)",
+        "$(execpath //main/cppu:cppu3_implib)",
+        "$(execpath //main/cppuhelper:cppuhelper_implib)",
+    ],
+    # The mini installation.  Paths are load-bearing: urebootstrap.ini resolves
+    # $ORIGIN against its OWN directory to reach basis/ and brand/, and the two
+    # bootstrap.ini files would collide under flat basename staging.
+    data_tree = {
+        "qa/unit/urebootstrap.ini": "unitdata/urebootstrap.ini",
+        ":qa_unit_uno_ini": "unitdata/basis/program/uno.ini",
+        ":qa_unit_bootstrap_ini_basis": "unitdata/basis/program/bootstrap.ini",
+        "qa/unit/data.xcd": "unitdata/basis/share/registry/data.xcd",
+        ":qa_unit_bootstrap_ini_brand": "unitdata/brand/program/bootstrap.ini",
+    },
+    ure_bootstrap = "unitdata/urebootstrap.ini",
+    # regcomp is staged beside the test exe (with its own CRT manifest) and run
+    # by the prerun lines below.
+    companions = ["//main/cpputools:regcomp"],
+    prerun = [
+        'copy /y "$(PROGRAM)\\types.rdb" "$(SCRATCH)\\unit.rdb" >nul',
+        '"$(RUNDIR)\\regcomp.exe" -register -br "$(PROGRAM)\\types.rdb" ' +
+        '-r "$(SCRATCH)\\unit.rdb" -c "$(PROGRAM)\\configmgr.uno.dll"',
+    ],
+    env = {
+        # Read with getenv() (test.cxx Test::SetUp), so NOT bootstrap-expanded:
+        # single backslashes, unlike arg-user in the office_connection fixture.
+        "CONFIGMGR_UNIT_FORWARD_STRING": "$(SCRATCH)\\unit.rdb",
+        "CONFIGMGR_UNIT_USER": "file:///$(SCRATCH_URL)/user",
+    },
+    # No runtime_dlls: with uno_install, co-locating a core UNO DLL breaks the
+    # bootstrap (cppuhelper resolves its component dir from its own module
+    # handle, and the exe's directory beats PATH).  program/ already has the
+    # whole closure, including configmgr.uno.dll itself.
+    uno_install = "//main/staging:install",
+)
+
diff --git a/main/configmgr/readme.md b/main/configmgr/readme.md
index 4fa0a9d4ea..c5e5998bb0 100644
--- a/main/configmgr/readme.md
+++ b/main/configmgr/readme.md
@@ -1,6 +1,126 @@
+<!--
+ 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.
+-->
+
 # Notes for configmgr (done)
 
 - Single DLL: `configmgr.uno.dll`
 - Deps: sal, cppu, cppuhelper, salhelper, comphelper, xmlreader, udkapi, 
offapi, stlport, boost.legacy
 - Requires `/Zc:wchar_t-` and stlport (uses boost::unordered_map, hash_map)
 - Exports via SAL_DLLPUBLIC_EXPORT — no DEF file
+
+## qa/unit — `//main/configmgr:configmgr_qa_unit`
+
+> **Status: wired and RED. It cannot go green without a source change.**
+>
+> It builds and links, then dies in the `prerun` fixture step with
+> `configmgr.uno.dll: cannot get symbol: component_writeInfo`.
+>
+> `regcomp -register` registers a component by calling `component_writeInfo()`
+> on it — the **pre-`.component` registration mechanism**. AOO retired that:
+> `configmgr/source/services.cxx` exports only `component_getFactory` and
+> `component_getImplementationEnvironment`, and the tree now has 438
+> `.component` files against a handful of `component_writeInfo` exporters (odk
+> examples, workbench samples, legacy tests). `qa/unit/makefile.mk` was never
+> updated to match.
+>
+> The obvious escape — hand it a **textual** (XML) services registry instead of
+> a binary one — is closed. `test.cxx` calls the *one-argument*
+> `createRegistryServiceFactory` overload, which defaults `bReadOnly` to
+> `sal_False`, so `SimpleRegistry::open()` receives `(readonly=false,
+> create=true)`; the textual branch is reachable only when
+> `bReadOnly && !bCreate`. `UNO_SERVICES` does not help either — that belongs 
to
+> `defaultBootstrap_InitialComponentContext`, not to this deprecated path.
+>
+> **Upstream knows.** The commit that moved this suite to GoogleTest
+> (`7231f715d2`, `#i125003#`) says so in its own message: *"All tests fail and 
on
+> Windows it doesn't start running, but these look like problems from before 
and
+> at least it doesn't run during the build."* dmake gates every `qa/` dir 
behind
+> `ENABLE_UNIT_TESTS=NO`, so nobody has run it since. Same class of bit-rot as
+> the missing `<windows.h>` in sal's `osl/process` suites — just fatal rather
+> than cosmetic.
+>
+> It stays wired because it *builds*, per the policy that a buildable test gets
+> wired even when red on its own merits (cf.
+> `//main/writerfilter:writerfilter_qa_doctok`, red on a fixture never checked
+> in). The `regcomp` line is deliberately left in: its error names the retired
+> mechanism exactly, which beats a vaguer failure later inside `Test::SetUp`.
+>
+> Fixing it means one of these, all **source** changes: port `test.cxx` to
+> `defaultBootstrap_InitialComponentContext` + a `services.rdb` (i.e. make it
+> fixture (a), already supported); or pass `sal_True` for `bReadOnly` and feed 
it
+> a textual services registry; or re-export `component_writeInfo` from 
configmgr
+> (reviving what AOO retired — the worst of the three).
+>
+> Everything below is verified working, and is why the target stays: the mini
+> installation stages and resolves, and the four `gtest_test` capabilities it
+> drove (`data_tree` / `ure_bootstrap` / `env` / `prerun`) are general.
+
+Mirrors the `TEST` target in `qa/unit/makefile.mk`.  A THIRD test fixture
+flavour, next to the two described in `main/test/readme.md`: not an in-process
+bootstrap of the staged office (a), and not a launched soffice over URP (b).
+The test builds its own service manager with
+`cppu::createRegistryServiceFactory(unit.rdb)` and runs against a four-file
+installation of its own, whose entire `share/registry` is the `data.xcd` it
+ships.  That is the point of it — the assertions are on
+`/org.openoffice.Setup Test/AString`, a key the real registry does not have, 
and
+the write cases must not touch a real user profile.
+
+Staged layout (`data_tree`, so the paths are exact — `urebootstrap.ini` 
resolves
+`$ORIGIN` against its own directory, and two files are named `bootstrap.ini`):
+
+    unitdata/urebootstrap.ini                  OOO_BASE_DIR=$ORIGIN/basis, 
BRAND_BASE_DIR=$ORIGIN/brand
+    unitdata/basis/program/uno.ini             generated
+    unitdata/basis/program/bootstrap.ini       generated  (UserInstallation)
+    unitdata/basis/share/registry/data.xcd     the whole registry under test
+    unitdata/brand/program/bootstrap.ini       generated  (same content)
+
+`ure_bootstrap` points `URE_BOOTSTRAP` at `unitdata/urebootstrap.ini` instead 
of
+the staged office's `program/fundamental.ini`; `uno_install` is still needed,
+because only the DATA root moves — the UNO DLL closure (including
+`configmgr.uno.dll` itself) still comes from `program/` via `PATH`.
+
+### Two divergences from the dmake recipe, both forced
+
+**`unit.rdb` is built by the launcher, not by a build action.**  `regcomp`
+bootstraps a UNO service manager, so as a build action it would need its own
+staged DLL closure and CRT manifest; worse, the location it records for a
+component is an ABSOLUTE path (`registercomponent.cxx::convertToFileUrl`), 
which
+has no business inside a cacheable artifact.  Built per run in the scratch dir,
+the path it bakes in is the staged install this run is actually using.
+`-br types.rdb` is added (dmake omits it) to keep regcomp's own bootstrap off 
the
+default registry — with it, cppuhelper builds the service manager from the type
+registry plus its hardcoded `bootstrap.uno.dll` components, so regcomp never
+consults the `URE_BOOTSTRAP` we just redirected at the mini installation.
+
+**`bootstrap.ini` is written to `basis/program/` as well as `brand/program/`.**
+The makefile writes it only under `brand/`, but AOO's
+`Components::getModificationFileUrl()` reads
+`${$OOO_BASE_DIR/program/bootstrap.ini:UserInstallation}` — `OOO_BASE_DIR`, 
i.e.
+`basis/`.  Under the recipe as written that lookup yields nothing and every 
write
+test silently loses its changes; the fixture is stale, not the code.  It is
+generated build data rather than source, so it is fixed here rather than worked
+around.  `UserInstallation` also cannot be the makefile's `$ORIGIN/../../user`,
+which resolves inside `bazel-out` — build output, to be treated as read-only.  
It
+indirects through `$CONFIGMGR_UNIT_USER`, which the launcher points at the
+per-run scratch dir (`writeModFile()` does `Directory::createPath`, so the leaf
+need not pre-exist).
+
+`CONFIGMGR_UNIT_FORWARD_STRING` (the path to `unit.rdb`) is read with plain
+`getenv()` in `Test::SetUp`, so unlike `arg-user` in fixture (b) it must NOT 
have
+its backslashes doubled — nothing macro-expands it.
diff --git a/main/test/readme.md b/main/test/readme.md
index 1f34fd0682..fd69423c74 100644
--- a/main/test/readme.md
+++ b/main/test/readme.md
@@ -133,11 +133,76 @@ holdouts).  This brings the test layer onto Bazel so 
suites run under
 
       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`.
+      That is why `office_connection` defaults `size` to `medium` (300 s) —
+      `small`'s 60 s is barely 3× the observed run, and the timeout is the only
+      thing that ends a run where the office never comes up.
 
       Green: `//main/test:test_qa_officeconnection` (~13 s — a full office 
boot,
       URP resolve, remote service creation and clean terminate).
 
+   c. **Own mini installation** — the test supplies its own registry data and
+      builds its own service manager, typically with
+      `cppu::createRegistryServiceFactory(<rdb>)`. Neither (a) nor (b): no
+      default bootstrap, no soffice. This is what a test *of the configuration
+      layer* needs, because it must assert on keys the real registry does not
+      have and write keys without touching a real user profile.
+
+      Four `gtest_test` attributes cover it, and none is specific to that one
+      test:
+
+      - **`data_tree = {label: "relative/path"}`** — staging at a path you
+        choose rather than flat by basename. Needed when the *shape* of the 
tree
+        is the fixture: `$ORIGIN` in a bootstrap ini resolves against that 
ini's
+        own directory, and a mini installation has two files named
+        `bootstrap.ini`.
+      - **`ure_bootstrap = "relative/path"`** — point `URE_BOOTSTRAP` at one of
+        those staged inis instead of the install's `program/fundamental.ini`.
+        Still requires `uno_install`: only the DATA root moves, and the UNO DLL
+        closure still comes from `program/` via `PATH`.
+      - **`env = {…}`** and **`prerun = […]`** — extra environment variables,
+        and command lines run in the launcher just before the exe (each
+        `|| exit /b 1`-guarded, so a fixture that fails to build fails the test
+        loudly). Both expand `$(RUNDIR)`, `$(PROGRAM)`, `$(SCRATCH)` and
+        `$(SCRATCH_URL)`.
+
+      `$(SCRATCH)` is a fresh, empty, **writable** directory under
+      `TEST_TMPDIR`, wiped before *and* after the run. Everything a test writes
+      belongs there: the staged directory lives in `bazel-out` and must be
+      treated as read-only build output. `office_connection`'s user 
installation
+      is just this same scratch dir.
+
+      `prerun` exists for fixtures that can only be built **at run time**. The
+      configmgr case is the archetype: its registry has to be written by
+      `regcomp`, a tool that bootstraps UNO — as a build action that would need
+      its own staged DLL closure and CRT manifest, and the component location 
it
+      records is an *absolute* path, which must not end up in a cacheable
+      artifact.
+
+      **Backslash rule, restated because it cuts both ways.** An `env` value 
the
+      test reads back through `rtl::Bootstrap::get()` is macro-expanded, so its
+      backslashes must be **doubled** (`arg-user`). One read with plain
+      `getenv()` must **not** be (`CONFIGMGR_UNIT_FORWARD_STRING`). Prefer a
+      `file:///` URL where the consumer accepts one — forward slashes sidestep
+      the question entirely.
+
+      Wired: `//main/configmgr:configmgr_qa_unit` — **red, and not fixable
+      without a source change**. The fixture machinery above works; the test
+      itself bootstraps through `createRegistryServiceFactory` + `regcomp
+      -register`, i.e. the pre-`.component` registration mechanism that AOO
+      retired, so `regcomp` cannot find `component_writeInfo` in
+      `configmgr.uno.dll`. Upstream's own GoogleTest-migration commit
+      (`7231f715d2`) records that the suite already failed and "on Windows it
+      doesn't start running". See `main/configmgr/readme.md` for the full
+      diagnosis, the staged layout, and the two forced divergences from
+      `qa/unit/makefile.mk`.
+
+      **Lesson for the remaining fixture work:** a `qa/` dir that dmake has
+      gated behind `ENABLE_UNIT_TESTS=NO` for a decade may encode a mechanism
+      the product no longer has. Check the test's *bootstrap path* against
+      current source before costing the fixture — the blocker recorded for this
+      one ("needs a custom `ure_bootstrap` root + extra env") was read off the
+      makefile and was not the real obstacle.
+
 ## Gotchas (learned the hard way)
 
 - **The C++/UNO bridge DLL is a run-time dep that nothing links.**

Reply via email to