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 c8a9767d256d1c86f1e632f9f4f22327070995b1
Author: Peter Kovacs <[email protected]>
AuthorDate: Wed Aug 5 22:58:10 2026 +0200

    test(testtools): bridgetest over URP — the round trip across a process 
boundary
    
    Wire dmake's bridgetest_server + bridgetest_client pair as
    //main/testtools:bridgetest_urp (~4s, green).  Same driver, same assertions
    and the same C++ object as :bridgetest, but the object now lives in a second
    process, so every call is marshalled by //main/binaryurp over TCP.
    
    This is the only test in the tree that puts a real object graph across a 
real
    connection — binaryurp's own qa/ suites cover its cache and unmarshal 
helpers
    in isolation, and nothing exercised acceptor/connector, the URP protocol,
    UnoUrlResolver and bridge lifetime together.  It is also wider than
    :bridgetest_java, which has to pass "noCurrentContext" because the Java 
object
    has no XCurrentContext to hand back: here that sub-test runs, making this 
the
    only check that a UNO current context propagates across a process boundary.
    
    The entire client/server split is one argument.  "-u <uno url>" placed AFTER
    "--" is an argument to BridgeTest::run(), not to unoexe, and the driver then
    resolves the object through com.sun.star.bridge.UnoUrlResolver instead of
    instantiating it locally.  The server is the same uno.exe with its own -u 
plus
    --singleaccept, so it serves one connection and exits.
    
    Two new general attributes on staged_run_test, since dmake only GENERATES 
the
    two .bat scripts (they are in ALLTAR but nothing runs them; only the 
in-process
    `runtest` executes) and there was no recipe to port:
    
      * server_args — stage `binary` a second time as <name>_server.exe and 
start
        it detached before the client.  The second staged name is what makes the
        cleanup `taskkill /f /im` precise; killing by a shared image name would
        take the client, and any concurrent test using the same tool, with it.
      * server_ready_port — poll until something is listening.  Not optional: 
the
        server needs about a second to load the UNO stack before it reaches
        accept(), and UnoUrlResolver::resolve() makes ONE connect() and throws
        NoConnectException.  It has to be netstat rather than a connect probe,
        because a probe that succeeds consumes the single connection
        --singleaccept will serve.
    
    The verdict is purely the client's exit code; the server is fixture, and any
    straggler is killed so a failed run cannot leave one blocked in accept()
    holding the port.  tags=["exclusive"] because port 2002 is upstream's
    hardcoded choice and is machine-global.
    
    LANDMINE — netstat's STATE column is LOCALIZED.  On a German Windows the
    listening row reads "ABHÖREN", not "LISTENING", so matching the state word
    makes the wait time out on every run for reasons that have nothing to do 
with
    the test.  Match the foreign address instead: a listening socket is the only
    kind whose peer is 0.0.0.0:0, and that is a number, not a word.  The 
trailing
    space after the port matters too, or ":2002" also matches ":20020".
    
    This is also the first place the known .uno component-DLL naming divergence
    has actually bitten, at exactly the site CLAUDE.md predicted 
("remote-UNO/URP
    bootstrap").  dmake gives the server only the two test registries and lets
    unoexe.cxx's createInstance() fallback loadSharedLibComponentFactory() the
    hardcoded names acceptor.uno.dll / connector.uno.dll / binaryurp.uno.dll; we
    emit acceptor.dll and friends, so that path can never fire.  The fix is not 
a
    rename but the registry the client already uses — nest the installation's 
own
    program/services.rdb into the server too and all three resolve BY SERVICE 
NAME
    on the first attempt, so the filename path is never reached.  Naming 
services
    is rename-proof; a rename would need services.rdb moved in lockstep.
    
    Also correct the frontier on the pyuno variant, recorded there as "reachable
    now that pyuno is wired".  It is blocked, and not on the fixture: main.py 
is a
    unittest suite whose first statement is addComponentsToContext(...,
    "com.sun.star.loader.SharedLibrary"), i.e. ImplementationRegistration ->
    DllComponentLoader::writeRegistryInfo -> writeSharedLibComponentInfo, which
    resolves component_writeInfo — the same retired pre-.component mechanism 
that
    blocks configmgr/qa/unit.  Registering the components in UNO_SERVICES does 
not
    help (the call is unconditional, before any test runs), and importer.py's
    testDynamicComponentRegistration repeats it with acceptor/connector, so it 
is
    the suite's premise rather than one line.  Independently, main.py never 
checks
    the runner result and never sys.exit()s, so a faithful port would be green
    whatever it reported.  Both fixes are source changes.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 CLAUDE.md                  |  65 ++++++++++++++++++++++--
 build/rules/gtest_test.bzl | 104 ++++++++++++++++++++++++++++++++++++--
 main/testtools/BUILD.bazel |  96 ++++++++++++++++++++++++++++++++++-
 main/testtools/readme.md   | 123 +++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 366 insertions(+), 22 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 04cb651cab..9e4190650d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -264,9 +264,11 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                    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 GREEN 2026-08-05, BOTH halves — 
//main/testtools:bridgetest
-                   (C++ object, ~1.1s) and :bridgetest_java (Java object over 
the
-                   java_uno JNI bridge, ~1.4s); :bridgetest_tests runs both.  
This is
+testtools     🔨  bridgetest GREEN 2026-08-05, ALL THREE halves — 
//main/testtools:
+                   bridgetest (C++ object in-process, ~1.1s), :bridgetest_java 
(Java
+                   object over the java_uno JNI bridge, ~1.4s) and 
:bridgetest_urp
+                   (C++ object in a SECOND PROCESS over socket URP, ~4s);
+                   :bridgetest_tests runs all three.  This is
                    the widest type-marshalling check in the tree (every simple 
type,
                    string, enum, struct, POLYMORPHIC struct, sequence, any, 
interface,
                    attribute, out/inout param, exception, multiple 
inheritance, current
@@ -315,9 +317,62 @@ testtools     🔨  bridgetest GREEN 2026-08-05, BOTH halves 
— //main/testtool
                    along.  Passed as a file:/// URL (unoexe convertToFileUrl 
takes it
                    verbatim).  //build:jre.bzl now centralizes the 
machine-specific,
                    arch-select()ed test JRE (was duplicated in cppuhelper).
+                   SOCKET-URP VARIANT GREEN 2026-08-05 — 
//main/testtools:bridgetest_urp
+                   (~4s), dmake's bridgetest_server + bridgetest_client pair.
+                   Same driver, same assertions, same C++ object, but in a 
SECOND PROCESS,
+                   so every call crosses //main/binaryurp over TCP.  Only test 
in the tree
+                   that puts a real object graph across a real connection 
(binaryurp's own
+                   qa/ covers cache+unmarshal in isolation); also WIDER than 
the Java half,
+                   which must pass noCurrentContext — here the current-context 
check runs,
+                   so it is the only proof a UNO current context propagates 
across a
+                   process boundary.  The whole client/server split is ONE 
argument: "-u
+                   <uno url>" AFTER "--" makes it an arg to BridgeTest::run(), 
which then
+                   resolves via UnoUrlResolver instead of instantiating 
locally.
+                   FIRST REAL BITE of the KNOWN .uno COMPONENT-NAMING 
DIVERGENCE, at
+                   exactly the site CLAUDE.md predicted ("remote-UNO/URP 
bootstrap"):
+                   dmake gives the SERVER only the two test registries and lets
+                   unoexe.cxx's createInstance() fallback 
loadSharedLibComponentFactory()
+                   the HARDCODED names acceptor.uno.dll / connector.uno.dll /
+                   binaryurp.uno.dll — we emit acceptor.dll etc., so that path 
can never
+                   fire.  FIX IS NOT A RENAME: nest the installation's own
+                   program/services.rdb into the server too (the Java half 
already does it
+                   for the client) and all three resolve BY SERVICE NAME on 
the first
+                   attempt, so the filename path is never reached.  Naming 
services is
+                   rename-proof; a rename would need services.rdb moved in 
lockstep.
+                   TWO NEW GENERAL gtest_test ATTRS: server_args (stage 
`binary` a SECOND
+                   time as <name>_server.exe and start it detached before the 
client —
+                   the second name is what makes cleanup `taskkill /f /im` 
precise; a
+                   shared image name would kill the client too) and 
server_ready_port
+                   (poll netstat until LISTENING).  The wait is NOT optional: 
the server
+                   needs ~1s to reach accept() and UnoUrlResolver::resolve() 
does ONE
+                   connect() then throws NoConnectException.  And it must be 
netstat, not
+                   a connect probe — a probe that succeeds CONSUMES the single 
connection
+                   --singleaccept will serve.  Verdict = the CLIENT's exit 
code only;
+                   stragglers are killed so a failed run cannot leave a server 
blocked in
+                   accept() holding the port.  tags=["exclusive"] — port 2002 
is
+                   upstream's hardcoded choice and is machine-global.  NOTE 
dmake only
+                   GENERATES the two .bat files (in ALLTAR but never run; only 
the
+                   in-process `runtest` executes), so there was no recipe to 
port.
                    See main/testtools/readme.md.  STILL ⬜: cli + cliversioning 
+ qa/cli
-                   (cli_ure bucket), pyuno variant (reachable now), 
source/performance
-                   (a benchmark), and the socket-URP client/server variants.
+                   (cli_ure bucket), source/performance (a benchmark), and
+                   bridgetest_javaserver (Java server over URP — needs the 
background
+                   process to be a `java` command line, which server_args does 
not
+                   express; its Java marshalling is already covered 
in-process).
+                   pyuno variant — ⬜ BLOCKED, and the old note "reachable now" 
was WRONG.
+                   It is not the same driver: main.py is a unittest suite 
whose FIRST
+                   statement is unohelper.addComponentsToContext(…,
+                   "com.sun.star.loader.SharedLibrary"), i.e.
+                   ImplementationRegistration → 
DllComponentLoader::writeRegistryInfo →
+                   cppuhelper writeSharedLibComponentInfo, which resolves
+                   component_writeInfo — the SAME RETIRED MECHANISM as 
configmgr/qa/unit.
+                   cppobj/bridgetest export only 
getImplementationEnvironment+getFactory
+                   ⇒ "cannot get symbol: component_writeInfo".  Putting them in
+                   UNO_SERVICES does not help (the call is unconditional, 
before any test),
+                   and importer.py's testDynamicComponentRegistration repeats 
it with
+                   acceptor.uno/connector.uno — it is the suite's PREMISE, not 
one line.
+                   Second, independent blocker: main.py never checks the 
runner result and
+                   never sys.exit()s, so a faithful port would be GREEN 
whatever it
+                   reported.  Fixing either is a source change.
 qadevOOo      🔨  OOoRunner.jar built (//main/qadevOOo:OOoRunner — qadevOOo QA
                    framework, ~2137 classes; classpath ridl/unoil/jurt/juh_jar/
                    java_uno_jar; .csv objdsc NOT jarred, manifest omitted).
diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl
index a5837a9689..4bab3b81a7 100644
--- a/build/rules/gtest_test.bzl
+++ b/build/rules/gtest_test.bzl
@@ -128,6 +128,61 @@ def _prerun_lines(prerun):
         out.append(_expand_tokens(cmd) + " || exit /b 1")
     return out
 
+def _server_lines(server_exe, server_args, port):
+    """Start the background server, then wait until it is actually listening.
+
+    `start "" /b` detaches without a console window; the empty first argument 
is
+    the window TITLE, and it has to be there — otherwise cmd reads the quoted
+    exe path as the title and there is nothing left to run.  The server 
inherits
+    the environment set above it (URE_BOOTSTRAP, PATH) and the working
+    directory, which is what makes its relative `-ro` registry names resolve.
+    """
+    lines = [
+        'start "" /b "%%~dp0%s"%s' % (server_exe, _arg_string(server_args)),
+    ]
+    if not port:
+        return lines
+
+    # Without this the test is a race the client usually loses: the server has
+    # to load the whole UNO stack before it calls accept(), which takes about a
+    # second, and the client's UnoUrlResolver::resolve() does ONE connect() and
+    # throws NoConnectException if nothing is listening yet.
+    #
+    # netstat rather than a connect probe, because a probe that succeeds has
+    # consumed the single connection --singleaccept will serve.
+    #
+    # LANDMINE: netstat's STATE column is LOCALIZED — on a German Windows the
+    # listening row reads "ABHÖREN", not "LISTENING", so matching the state 
word
+    # makes the wait time out on every run for reasons that have nothing to do
+    # with the test.  Match the FOREIGN ADDRESS instead: a listening socket is
+    # the only kind whose peer is 0.0.0.0:0, and that is a number, not a word.
+    # Both patterns have to hit the SAME line, which the pipeline gives for 
free.
+    #
+    # The trailing space after the port is load-bearing too: without it ":2002"
+    # also matches ":20020".
+    #
+    # `ping -n 2` is the portable batch sleep (~1s).  timeout.exe is the modern
+    # spelling but aborts with "Input redirection is not supported" whenever
+    # stdin is not a console, which under `bazel test` it never is.
+    return lines + [
+        "set /a _TRY=0",
+        ":_waitport",
+        'netstat -an | findstr /c:"127.0.0.1:%s " | findstr /c:"0.0.0.0:0" 
>nul' % port,
+        "if not errorlevel 1 goto _portup",
+        "set /a _TRY+=1",
+        # ~30s: the server needs about one to reach accept(), so this is pure
+        # headroom, and it has to stay well inside the "small" 60s test budget
+        # or bazel's timeout would preempt the readable error below.
+        "if %_TRY% GEQ 30 goto _porttimeout",
+        "ping -n 2 127.0.0.1 >nul",
+        "goto _waitport",
+        ":_porttimeout",
+        'echo ERROR: server never listened on 127.0.0.1:%s 1>&2' % port,
+        'taskkill /f /im "%s" >nul 2>nul' % server_exe,
+        "exit /b 1",
+        ":_portup",
+    ]
+
 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.
@@ -179,6 +234,20 @@ def _staged_gtest_test_impl(ctx):
     ctx.actions.symlink(output = man, target_file = ctx.file.app_manifest)
     staged.append(man)
 
+    # A client/server test runs the SAME binary twice, so the background half 
is
+    # staged a second time under its own name.  That is not cosmetic: it is 
what
+    # makes the cleanup `taskkill /f /im` precise.  Killing by the shared image
+    # name would take the client — and any concurrently running test using the
+    # same tool — down with it.
+    server_exe_name = ctx.label.name + "_server.exe"
+    if ctx.attr.server_args:
+        srv = ctx.actions.declare_file(d + "/" + server_exe_name)
+        ctx.actions.symlink(output = srv, target_file = ctx.executable.binary)
+        staged.append(srv)
+        srvman = ctx.actions.declare_file(d + "/" + server_exe_name + 
".manifest")
+        ctx.actions.symlink(output = srvman, target_file = 
ctx.file.app_manifest)
+        staged.append(srvman)
+
     # 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
@@ -256,8 +325,11 @@ def _staged_gtest_test_impl(ctx):
     env = ctx.attr.env
     prerun = ctx.attr.prerun
     run_args = ctx.attr.run_args
+    server_args = ctx.attr.server_args
     argstr = _arg_string(run_args)
-    values = env.values() + prerun + run_args
+    srvlines = (_server_lines(server_exe_name, server_args, 
ctx.attr.server_ready_port)
+                if server_args else [])
+    values = env.values() + prerun + run_args + server_args
     need_scratch = (ctx.attr.office_connection or
                     _uses_token(values, "$(SCRATCH"))
     if _uses_token(values, "$(PROGRAM)") and not uno_program_dir:
@@ -265,9 +337,12 @@ def _staged_gtest_test_impl(ctx):
     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.")
+    if ctx.attr.server_ready_port and not server_args:
+        fail("server_ready_port without server_args — there is no server to 
wait for.")
 
     executable = staged_exe
-    if (ctx.attr.run_in_staged_dir or uno_program_dir or env or prerun or 
run_args):
+    if (ctx.attr.run_in_staged_dir or uno_program_dir or env or prerun or
+        run_args or server_args):
         launcher_dir = staged_exe.dirname  # the .bat sits beside the staged 
exe
         lines = ["@echo off", "setlocal"]
 
@@ -395,7 +470,7 @@ def _staged_gtest_test_impl(ctx):
                     'set "arg-soffice=path:%_SOFFICE:\\=\\\\%"',
                     'set "arg-user=%_SCRATCH:\\=\\\\%"',
                 ]
-            lines += _env_lines(env) + _prerun_lines(prerun)
+            lines += _env_lines(env) + _prerun_lines(prerun) + srvlines
             lines += ['"%_EXE%"' + argstr + " %*"]
         else:
             # Co-locating a data file with the exe is not enough for a test 
that
@@ -406,11 +481,18 @@ def _staged_gtest_test_impl(ctx):
             # 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 += _env_lines(env) + _prerun_lines(prerun) + srvlines
             lines += ['"%~dp0' + staged_exe.basename + '"' + argstr + " %*"]
 
         # Capture the exit code BEFORE any cleanup — rmdir would clobber it.
         lines += ['set "_RC=%ERRORLEVEL%"']
+        if server_args:
+            # Normally a no-op: a --singleaccept server exits on its own once 
the
+            # client drops the connection.  It is the FAILURE path that needs
+            # this — a client that died before connecting would otherwise leave
+            # the server blocked in accept() forever, holding the port and
+            # poisoning every later run.
+            lines += ['taskkill /f /im "%s" >nul 2>nul' % server_exe_name]
         if need_scratch:
             lines += ['rmdir /s /q "%_SCRATCH%" 2>nul']
         lines += ["exit /b %_RC%", ""]
@@ -442,6 +524,8 @@ _staged_gtest_test = rule(
         "env": attr.string_dict(),
         "prerun": attr.string_list(),
         "run_args": attr.string_list(),
+        "server_args": attr.string_list(),
+        "server_ready_port": attr.string(),
         "ure_bootstrap": attr.string(),
     },
 )
@@ -459,6 +543,18 @@ _staged_gtest_test = rule(
 # command line still follows it, via %*.  Deliberately not the native `args`
 # attribute: baking it in keeps `bazel run` on the target reproducing exactly
 # what `bazel test` ran.
+#
+# server_args / server_ready_port: a CLIENT/SERVER test.  The same `binary` is
+# staged a second time as "<name>_server.exe" and started detached with
+# server_args (token-expanded like run_args) just before the client runs; the
+# launcher then waits until something is LISTENING on server_ready_port, and
+# kills any surviving server afterwards.  The test result is still purely the
+# CLIENT's exit code — the server is fixture, not verdict.
+#
+# It exists for testtools' bridgetest_urp, where the round trip has to cross a
+# process boundary to exercise the URP bridge at all; the in-process variants
+# need none of it.  A test using it should carry tags = ["exclusive"], since 
the
+# port is a fixed, machine-global resource (upstream hardcodes 2002).
 staged_run_test = _staged_gtest_test
 
 def gtest_test(
diff --git a/main/testtools/BUILD.bazel b/main/testtools/BUILD.bazel
index 80e71c46a2..dba8b2b03a 100644
--- a/main/testtools/BUILD.bazel
+++ b/main/testtools/BUILD.bazel
@@ -409,12 +409,104 @@ staged_run_test(
     size = "small",
 )
 
-# Both halves of the round trip: the same driver and the same assertions, once
-# against a C++ object and once against a Java one.
+# ── The same round trip, over a socket ────────────────────────────────────
+# dmake's `bridgetest_server` + `bridgetest_client` pair.  Same driver, same
+# assertions, same C++ object as :bridgetest — but the object now lives in
+# ANOTHER PROCESS, so every call is marshalled by the binary URP bridge
+# (//main/binaryurp) over a TCP connection instead of staying in-process.
+#
+# That makes it the only test of the remote-UNO path as a whole: acceptor and
+# connector (//main/io), the URP protocol itself, UnoUrlResolver
+# (//main/remotebridges), and bridge lifetime/disposal.  binaryurp's own qa/
+# suites cover its cache and unmarshal helpers in isolation; nothing until now
+# put a real object graph across a real connection.
+#
+# It is also WIDER than :bridgetest_java, which has to pass "noCurrentContext"
+# because the Java object cannot supply one.  Here the current-context check
+# runs, i.e. this is the only test that a UNO current context propagates across
+# a process boundary.
+#
+# dmake only ever GENERATES the two .bat scripts (they are in ALLTAR, but
+# nothing runs them; only the in-process `runtest` is executed automatically),
+# so the orchestration below has no recipe to copy — hence server_args /
+# server_ready_port in gtest_test.bzl.  The verdict is the CLIENT's exit code;
+# the server is fixture.  --singleaccept makes it serve one connection and 
exit,
+# and the launcher kills any straggler so a failed run cannot leave the port
+# held.  tags=["exclusive"] because port 2002 is upstream's hardcoded choice 
and
+# is a machine-global resource.
+#
+# DIVERGENCE, and it is the KNOWN component-DLL NAMING divergence finally
+# biting: dmake gives the SERVER only the two test registries and lets
+# unoexe.cxx's fallback in createInstance() cover the rest — when a service is
+# missing it loadSharedLibComponentFactory()s "acceptor.uno.dll",
+# "connector.uno.dll" and "binaryurp.uno.dll" by HARDCODED NAME.  Bazel emits
+# those as acceptor.dll / connector.dll / binaryurp.dll (see CLAUDE.md), so 
that
+# fallback can never fire here.  The fix is not a rename but the registry the
+# client already uses: with the installation's own services.rdb nested in, all
+# three services resolve by NAME on the first attempt and the fallback is never
+# reached.  This is exactly the failure mode CLAUDE.md predicted for the
+# divergence ("remote-UNO/URP bootstrap"), and the reason it is harmless.
+staged_run_test(
+    name = "bridgetest_urp",
+    binary = "//main/cpputools:uno",
+    server_args = [
+        "-ro",
+        "file:///$(PROGRAM_URL)/services.rdb",
+        "-ro",
+        "uno_services.rdb",
+        "-ro",
+        "uno_types.rdb",
+        "-s",
+        "com.sun.star.test.bridge.CppTestObject",
+        "-u",
+        "uno:socket,host=127.0.0.1,port=2002;urp;test",
+        "--singleaccept",
+    ],
+    server_ready_port = "2002",
+    # "-u" here is NOT unoexe's own option — it comes after "--", so it is an
+    # argument to BridgeTest::run(), which reads it as "resolve the object at
+    # this URL" instead of "instantiate this service locally" (bridgetest.cxx).
+    # That one difference is the entire client/server split.
+    run_args = [
+        "-ro",
+        "file:///$(PROGRAM_URL)/services.rdb",
+        "-ro",
+        "uno_services.rdb",
+        "-ro",
+        "uno_types.rdb",
+        "-s",
+        "com.sun.star.test.bridge.BridgeTest",
+        "--",
+        "-u",
+        "uno:socket,host=127.0.0.1,port=2002;urp;test",
+    ],
+    run_in_staged_dir = True,
+    # Test-only artifacts ONLY, as in :bridgetest_java and for the same reason 
—
+    # co-locating cppuhelper3MSC.dll alongside uno_install would make
+    # cppu::get_unorc() miss program/uno.ini and silently expand every
+    # vnd.sun.star.expand: URI to nothing.  Both processes get the core stack
+    # from program/ via PATH.
+    runtime = [
+        ":bridgetest.uno",
+        ":constructors.uno",
+        ":cppobj.uno",
+        ":bridgetest_uno_services",
+        ":bridgetest_uno_types",
+        "//main/external/msvcp90:crt_dlls",
+    ],
+    uno_install = "//main/staging:install",
+    size = "small",
+    tags = ["exclusive"],
+)
+
+# Every half of the round trip: the same driver and the same assertions, once
+# against a C++ object in-process, once against a Java one, and once against a
+# C++ object in another process over URP.
 test_suite(
     name = "bridgetest_tests",
     tests = [
         ":bridgetest",
         ":bridgetest_java",
+        ":bridgetest_urp",
     ],
 )
diff --git a/main/testtools/readme.md b/main/testtools/readme.md
index 23953ac06d..0626634a8e 100644
--- a/main/testtools/readme.md
+++ b/main/testtools/readme.md
@@ -17,7 +17,7 @@
  under the License.
 -->
 
-# Notes for testtools (bridgetest: C++ and Java green)
+# Notes for testtools (bridgetest green: in-process C++ and Java, plus C++ 
over URP)
 
 `bridgetest` is the UNO bridge round-trip suite: every construct the type
 system has — each simple type, strings, enums, structs, polymorphic structs,
@@ -25,14 +25,18 @@ sequences, anys, interfaces, attributes, out/inout 
parameters, exceptions,
 multiple inheritance, the current context, recursive and sequence-of-calls
 dispatch — pushed through a call chain and checked coming back.
 
-Two targets, one driver:
+Three targets, one driver:
 
 | target | object under test | fixture | time |
 | --- | --- | --- | --- |
-| `//main/testtools:bridgetest` | `CppTestObject` (C++) | self-contained | 
~1.1 s |
-| `//main/testtools:bridgetest_java` | `JavaTestObject` (Java) | `uno_install` 
| ~1.4 s |
+| `//main/testtools:bridgetest` | `CppTestObject` (C++), in-process | 
self-contained | ~1.1 s |
+| `//main/testtools:bridgetest_java` | `JavaTestObject` (Java), in-process | 
`uno_install` | ~1.4 s |
+| `//main/testtools:bridgetest_urp` | `CppTestObject` in a **second process**, 
over socket URP | `uno_install`, client/server | ~4 s |
 
-`//main/testtools:bridgetest_tests` runs both.
+`//main/testtools:bridgetest_tests` runs all three. The first two check that
+the *bridges* marshal correctly (`msci_uno`/`mscx_uno` and `java_uno`); the
+third checks that the *remote* path does — a different bridge implementation
+(`//main/binaryurp`) over a real connection.
 
 ## It is not a GoogleTest, and should not be made into one
 
@@ -149,6 +153,72 @@ keys in `program/fundamental.ini`, and `jvmfwk.dll` finds 
`javavendors.xml`,
 `jvmfwk3.ini`, `sunjavaplugin.dll` and `JREProperties.class` beside itself
 there. One `URE_BOOTSTRAP` supplies the lot with no list to keep in sync.
 
+## The URP target — the round trip across a process boundary
+
+`//main/testtools:bridgetest_urp` is dmake's `bridgetest_server` +
+`bridgetest_client` pair. Same driver, same assertions, same C++ object as
+`:bridgetest` — but the object lives in **another process**, so every call is
+marshalled by the binary URP bridge over a TCP connection.
+
+The single difference on the command line is that `-u <uno url>` comes *after*
+`--`, which makes it an argument to `BridgeTest::run()` rather than to 
`unoexe`:
+the driver then resolves the object through
+`com.sun.star.bridge.UnoUrlResolver` instead of instantiating it locally
+(`bridgetest.cxx` around the `remote` flag). The server side is the same
+`uno.exe` with its *own* `-u`, plus `--singleaccept` so it serves one
+connection and exits.
+
+It is the only test in the tree that puts a real object graph across a real
+connection: acceptor and connector (`//main/io`), the URP protocol
+(`//main/binaryurp`, whose own qa/ suites cover only its cache and unmarshal
+helpers in isolation), `UnoUrlResolver` (`//main/remotebridges`), and bridge
+lifetime. It is also **wider than the Java target**, which must pass
+`noCurrentContext` because the Java object has no `XCurrentContext` to hand
+back — here that sub-test runs, so this is the only check that a UNO current
+context propagates across a process boundary.
+
+### The `.uno` naming divergence, finally biting
+
+dmake gives the **server** only the two test registries and lets `unoexe.cxx`
+cover the rest: when `createInstance()` cannot find a service it falls back to
+`loadSharedLibComponentFactory()` on the hardcoded names `acceptor.uno.dll`,
+`connector.uno.dll` and `binaryurp.uno.dll`.
+
+Bazel emits those as `acceptor.dll` / `connector.dll` / `binaryurp.dll` (the
+known component-DLL naming divergence in CLAUDE.md), so that fallback can never
+fire here — and this is the *first* place the divergence has actually mattered.
+CLAUDE.md predicted exactly this site ("remote-UNO/URP bootstrap").
+
+The fix is not a rename. The server gets the installation's own
+`program/services.rdb` nested in, the same third registry the Java target
+already uses, and all three services then resolve **by service name** on the
+first attempt, so the hardcoded-filename path is never reached. A rename would
+have to move `services.rdb` in lockstep; naming the services does not.
+
+### Orchestration: `server_args` / `server_ready_port`
+
+There is no dmake recipe to copy. dmake only *generates* the two `.bat` scripts
+— they are in `ALLTAR`, but nothing runs them; only the in-process `runtest` is
+executed automatically. So `gtest_test.bzl` grew two attributes:
+
+- **`server_args`** — stage `binary` a second time as `<name>_server.exe` and
+  start it detached (`start "" /b`) just before the client. A second staged
+  name rather than reusing the client's is what makes the cleanup
+  `taskkill /f /im` precise; killing by a shared image name would take the
+  client with it.
+- **`server_ready_port`** — poll `netstat` until something is LISTENING there.
+  Without it the test is a race the client usually loses: the server needs
+  about a second to load the UNO stack before it reaches `accept()`, and
+  `UnoUrlResolver::resolve()` makes **one** `connect()` and throws
+  `NoConnectException`. A connect probe would be worse than useless — a probe
+  that succeeds consumes the single connection `--singleaccept` will serve, so
+  it has to be `netstat`.
+
+The verdict is purely the **client's** exit code; the server is fixture. Any
+straggling server is killed afterwards, so a client that dies before connecting
+cannot leave one blocked in `accept()` holding the port. `tags = ["exclusive"]`
+because port 2002 is upstream's hardcoded choice and is machine-global.
+
 ## Build notes
 
 - `/Zc:wchar_t-` throughout — `sal_Unicode` is not VS2008's native `wchar_t`.
@@ -170,14 +240,45 @@ there. One `URE_BOOTSTRAP` supplies the lot with no list 
to keep in sync.
 
 - `source/bridgetest/cli` (C#/VB round trip) — blocked on the `cli_ure`
   bucket's C# and C++/CLI toolchains.
-- `source/bridgetest/pyuno` — the Python variant of the same driver; reachable
-  now that pyuno is wired, not yet done.
+- `source/bridgetest/pyuno` — **BLOCKED on a retired mechanism, not on the
+  fixture.** It was recorded here as "reachable now that pyuno is wired", which
+  was wrong: the Python variant is not the same driver at all. It is a
+  `unittest` suite (`main.py` → `importer` / `core` / `impl`), and its very
+  first statement registers the C++ objects dynamically:
+
+  ```python
+  unohelper.addComponentsToContext(
+      ctx, ctx, (FOO+"/cppobj.uno", FOO+"/bridgetest.uno", …),
+      "com.sun.star.loader.SharedLibrary")
+  ```
+
+  That path is `com.sun.star.registry.ImplementationRegistration` →
+  `DllComponentLoader::writeRegistryInfo()` →
+  `cppuhelper::writeSharedLibComponentInfo()`, which resolves
+  **`component_writeInfo`** — the pre-`.component` registration mechanism.
+  `cppobj.cxx` and `bridgetest.cxx` export only
+  `component_getImplementationEnvironment` and `component_getFactory`, so
+  `shlib.cxx` throws `cannot get symbol: component_writeInfo`. Registering the
+  components in `UNO_SERVICES` instead does not help — the call is made
+  unconditionally, before any test runs — and `importer.py`'s
+  `testDynamicComponentRegistration` does the same thing again with
+  `acceptor.uno` / `connector.uno`, so it is the suite's premise, not one line.
+
+  This is the **same wall as `configmgr/qa/unit`** (see CLAUDE.md): a qa/ dir
+  gated off for a decade encoding a mechanism the product no longer has. Fixing
+  it means porting `main.py` to a services registry, i.e. a source change.
+  Note also that `main.py` never checks the runner's result and never calls
+  `sys.exit()`, so a faithful port would be **green whatever it reported** —
+  a second reason it cannot be wired as-is.
 - `source/performance` (`perftest.uno`) — a benchmark, not a correctness test.
   Note `TestComponent.java` *does* implement
   `com.sun.star.test.performance.XPerformanceTest`, whose IDL lives in udkapi,
   so the Java half of it is already compiled here.
 - `source/cliversioning`, `qa/cli`, `qa/cliversioning` — `cli_ure` bucket.
-- `bridgetest_server` / `bridgetest_client` / `bridgetest_javaserver` — the
-  socket-URP variants of the same driver. They need an acceptor and a second
-  process; the in-process targets cover the marshalling, and URP itself is
-  covered by `//main/binaryurp` and `//main/test:test_qa_officeconnection`.
+- `bridgetest_javaserver` — the socket-URP variant with a **Java** server
+  (`com.sun.star.comp.bridge.TestComponentMain … singleaccept`). The C++
+  client/server pair is now wired as `:bridgetest_urp`; this one additionally
+  needs the background process to be a `java` command line rather than a second
+  copy of `binary`, which `server_args` does not currently express. Its
+  coverage — Java marshalling — is already reached in-process by
+  `:bridgetest_java`; what it would add is Java *over URP*.

Reply via email to