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 1bdcd7a4dee1c0603f0a084f00e5b14e13024878
Author: Peter Kovacs <[email protected]>
AuthorDate: Fri Aug 7 01:05:24 2026 +0200

    test(unoapi): the whole qa/unoapi category — all 18 suites from one macro
    
    They are not 18 tests but 18 sets of DATA for one runner, so they are one
    macro.  unoapi_test (build/rules/junit_test.bzl) takes only the four things
    that actually differ between the JunitTest_<module>_unoapi.mk files it 
ports —
    module, sce, xcl, tdoc — and `module` is the source directory name AND the
    adapter package (org.openoffice.<module>.qa.unoapi.Test) for all 18 without
    exception.  svtools and qadevOOo move onto it too.
    
    The recorded gate was wrong on its key fact.  -tdoc is NOT 
qadevOOo/testdocs:
    every makefile that sets it points at <module>/qa/unoapi/testdocuments, ten
    small per-module directories (~570 KB).  qadevOOo/testdocs is only
    util.utils.getFullTestDocName's fallback when the argument is absent AND
    SRC_ROOT is set — a dmake-tree assumption no wired suite reaches.
    
    The gate that WAS real is data_dirs, the directory-preserving companion to
    data_tree (in gtest_test.bzl, shared by both launchers).  data_tree maps one
    label to one path, which is right when a file's exact placement is under 
test,
    and wrong for a document ROOT: the runner joins names onto -tdoc at run 
time,
    so which documents a scenario opens is not a build-time fact — and
    dbaccess/forms have testdocuments/TestDB/, a nested directory data_tree 
cannot
    express at all.  Each entry stages every file of a label under the chosen
    directory at its path relative to their LONGEST COMMON DIRECTORY: the strip
    prefix is derived, not declared, which is exact for the glob(["<dir>/**"])
    filegroup the macro generates and keeps the call site to the one fact it 
knows.
    
    All 18 analyse clean; 16 have not been run yet.  Reds recorded at the call
    site: dbaccess (its -ini props name a MySQL server no developer box has; 
only
    ORowSet and OSingleSelectQueryComposer reach for it) and sc (43 active 
objects
    vs 67 commented out upstream, each with its issue number, left verbatim).
    
    toolkit HAS been run, and is red on a real office defect — an ORPHANED MUTEX
    in acc.dll, diagnosed with cdb rather than guessed.  The fixture is fine: 
the
    first object passes four whole interfaces, then the office hangs (Windows
    Application Hang event 1002, not a crash) and takes the other 52 with it.  
The
    main thread holds the SolarMutex in a WinProc focus dispatch and blocks in
    acc.dll's osl_acquireMutex; a binaryurp worker blocks on the same mutex; and
    !locks names that critical section (LockCount 2, exactly those two waiters)
    with an OwningThread that IS NOT IN THE LIVE THREAD LIST.  So it is not a
    lock-order inversion and not the qadevOOo shutdown deadlock — a thread 
exited
    holding the lock, and an osl mutex is a plain CRITICAL_SECTION, so nothing
    recovers in-process.  fixture_starts_office would not help; the hang is
    mid-run.  Reachable by any assistive technology that attaches to a running
    office, not just this test.  Fixing it is a source change in
    main/accessibility, so the suite stays wired and red, same disposition as
    //main/bridges:test_any_jni.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 CLAUDE.md                   |  86 +++++++++++++++++++++++++++-----
 build/rules/gtest_test.bzl  |  60 ++++++++++++++++++++--
 build/rules/junit_test.bzl  | 115 +++++++++++++++++++++++++++++++++++++++++-
 main/chart2/BUILD.bazel     |  20 ++++++++
 main/configmgr/BUILD.bazel  |  17 +++++++
 main/dbaccess/BUILD.bazel   |  24 +++++++++
 main/extensions/BUILD.bazel |  16 ++++++
 main/forms/BUILD.bazel      |  19 +++++++
 main/framework/BUILD.bazel  |  18 +++++++
 main/linguistic/BUILD.bazel |  17 +++++++
 main/qadevOOo/BUILD.bazel   |  25 ++--------
 main/sc/BUILD.bazel         |  19 +++++++
 main/sd/BUILD.bazel         |  16 ++++++
 main/sfx2/BUILD.bazel       |  17 +++++++
 main/starmath/BUILD.bazel   |  16 ++++++
 main/svtools/BUILD.bazel    |  39 ++++-----------
 main/svx/BUILD.bazel        |  19 ++++++-
 main/sw/BUILD.bazel         |  20 ++++++++
 main/test/readme.md         | 119 ++++++++++++++++++++++++++++++++++++++++----
 main/toolkit/BUILD.bazel    |  58 +++++++++++++++++++++
 main/ucb/BUILD.bazel        |  17 +++++++
 main/xmloff/BUILD.bazel     |  16 ++++++
 22 files changed, 694 insertions(+), 79 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 3bb50a4efe..643e930908 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -352,16 +352,74 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                    JVM.  JAVA_HOME is pinned to the same JDK because 
OfficeConnection
                    always passes -env:UNO_JAVA_JFW_ENV_JREHOME=true (the 
OFFICE's jvmfwk
                    then reads it).
-                   THE QUEUE IS LARGE AND WAS UNDER-COUNTED HERE: 18 qa/unoapi 
+ 24
-                   qa/complex dirs, not the 4 modules this bucket used to 
name.  Two
-                   gates on the rest: (i) -tdoc — 11 of the 18 unoapi adapters 
pass a
-                   test-document root ⇒ qadevOOo/testdocs must be staged, and 
data_tree
-                   stages one FILE per entry, so a directory-preserving 
variant is the
-                   next rule work (svtools went first precisely because its 
scenario
-                   builds its document via SOfficeFactory and needs none); 
(ii) per-module
-                   fixtures — svl/qa/complex/ConfigItems needs a C++ helper 
component,
-                   and several qa/complex dirs (writerfilter's among them) 
have no
-                   makefile.mk at all, i.e. were never wired upstream either.
+                   qa/unoapi — WHOLE CATEGORY WIRED 2026-08-06, all 18, every 
one
+                   //main/<module>:qa_unoapi.  They are not 18 tests but 18 
sets of DATA
+                   for one runner, so they are ONE MACRO — unoapi_test 
(junit_test.bzl)
+                   over the only four things that differ: module (= the source 
dir name,
+                   which is ALSO the adapter package 
org.openoffice.<module>.qa.unoapi
+                   .Test for all 18 without exception), sce, xcl, tdoc.  
Mirrors the
+                   JunitTest_<module>_unoapi.mk files, which differ only in 
their
+                   -Dorg.openoffice.test.arg.<name> lines.  ANALYSED CLEAN 
(bazel build
+                   --nobuild, all 18); NOT YET RUN — 16 of them have never 
executed here.
+                   THE RECORDED -tdoc GATE WAS WRONG on the key fact: it is NOT
+                   qadevOOo/testdocs.  Every makefile that sets -tdoc points at
+                   $(SRCDIR)/<module>/qa/unoapi/testdocuments — 10 small 
per-module dirs,
+                   ~570 KB total (linguistic points at qa/unoapi ITSELF, i.e. 
passing
+                   something non-null; neither of its 2 objects opens a 
document).
+                   qadevOOo/testdocs is only the FALLBACK util.utils 
getFullTestDocName
+                   takes when the arg is absent AND SRC_ROOT is set — a 
dmake-tree
+                   assumption no wired suite reaches.  So staging it is NOT a 
gate;
+                   OOoRunnerLight/testdocs stay ⬜ for other reasons.
+                   The gate that WAS real: data_dirs, the directory-preserving 
companion
+                   to data_tree (gtest_test.bzl, shared by both launchers).  
data_tree
+                   maps one label→one path, right when exact PLACEMENT is 
under test (a
+                   mini installation has two files named bootstrap.ini); wrong 
for a
+                   document ROOT, since the runner joins a name onto -tdoc AT 
RUN TIME so
+                   which documents a scenario opens is not a build-time fact — 
and
+                   dbaccess/forms testdocuments/TestDB/ is a NESTED dir 
data_tree cannot
+                   express at all.  Each entry stages every file of the label 
under the
+                   chosen dir at its path relative to the LONGEST COMMON 
DIRECTORY of
+                   that label's files: the strip prefix is DERIVED, not 
declared, which
+                   is exact for the glob(["<dir>/**"]) filegroup the macro 
generates and
+                   keeps the call site to the one fact it knows.
+                   FIRST ONE RUN — //main/toolkit:qa_unoapi, RED on an 
ORPHANED MUTEX in
+                   acc.dll (main/accessibility), left red.  Fixture is FINE: 
it connects,
+                   stages testdocuments/, and the first object
+                   (toolkit.AccessibleDropDownComboBox = the Find toolbar 
combo) passes
+                   FOUR whole interfaces; then the office HANGS and takes the 
other 52
+                   with it.  cdb -pv (non-invasive, on the live hang): Windows 
logs
+                   Application Hang event 1002, NOT a crash (Responding=False) 
— the Java
+                   EOFException/DisposedException are only the consequence; 
the MAIN
+                   thread holds the SolarMutex inside a WinProc dispatch
+                   (Application::Execute→Yield→DispatchMessageW) delivering a 
focus event
+                   
ImplGrabFocus→ImplCallActivateListeners→VclEventListeners::Call→
+                   ootk!VCLXAccessibleComponent::WindowEventListener→acc.dll→
+                   sal3!osl_acquireMutex and BLOCKS; a binaryurp worker (the 
test's UNO
+                   call) blocks on the SAME acc mutex; and !locks names the 
contended CS
+                   (LockCount 2 = exactly those two waiters) whose 
OwningThread IS NOT IN
+                   THE LIVE THREAD LIST.  So NOT a lock-order inversion and 
NOT the
+                   qadevOOo shutdown deadlock — the lock is ORPHANED: a thread 
exited
+                   holding it, and an osl mutex is a plain CRITICAL_SECTION, 
so it can
+                   never be acquired again and nothing recovers in-process.  A 
real
+                   product defect reachable by ANY assistive technology 
attaching to a
+                   running office, not just this test; fix = source change in
+                   main/accessibility, out of scope.  Upstream's 
knownissues.xcl excludes
+                   toolkit.AccessibleComboBox but NOT 
AccessibleDropDownComboBox, i.e.
+                   upstream thinks the object is testable — consistent with a 
qa/ dir
+                   gated behind ENABLE_UNIT_TESTS=NO for a decade that never 
ran.
+                   TWO MORE REDS EXPECTED, both recorded at the call site: 
dbaccess (the only
+                   -ini adapter; dbaccess.props names a MySQL server no dev 
box has, so
+                   ORowSet + OSingleSelectQueryComposer fail and the other 7 
objects do
+                   not — wired anyway, a partial red naming which objects need 
a DB beats
+                   an unwired suite) and sc (43 active objects vs 67 COMMENTED 
OUT, each
+                   with its issue number — the honest upstream state, left 
verbatim).
+                   Long poles size="large": sw (75 objects), toolkit (53), sc 
(43),
+                   forms (34).
+                   STILL ⬜: the 24 qa/complex dirs — unlike unoapi these are 
hand-written
+                   suites with no shared shape, so one at a time.  Gates: 
per-module
+                   fixtures (svl/qa/complex/ConfigItems needs a C++ helper 
component) and
+                   several (writerfilter's among them) have no makefile.mk at 
all, i.e.
+                   were never wired upstream either.
                    See main/test/readme.md.
                    OPEN RED, LEFT RED ON PURPOSE — //main/bridges:test_any_jni.
                    NOT a fixture problem and NOT caused by anything above: it 
fails in
@@ -514,8 +572,12 @@ qadevOOo      🔨  OOoRunner.jar built 
(//main/qadevOOo:OOoRunner — qadevOOo
                    JunitTest_qadevOOo_unoapi GREEN 2026-08-06 (//main/qadevOOo:
                    qa_unoapi, ~16s) via uno_junit_test fixture_starts_office — 
see
                    the OFFICE SHUTDOWN DEADLOCK note in the test bucket.
-                   STILL ⬜: OOoRunnerLight, testdocs (needed by 11 of the 18
-                   unoapi adapters, which pass -tdoc).
+                   STILL ⬜: OOoRunnerLight, testdocs.  CORRECTION — testdocs is
+                   NOT what the 11 -tdoc adapters need: they each point at 
their
+                   OWN <module>/qa/unoapi/testdocuments (now staged per module 
via
+                   data_dirs).  qadevOOo/testdocs is only 
utils.getFullTestDocName's
+                   fallback when -tdoc is absent AND SRC_ROOT is set, which no
+                   wired suite hits.  It is a leftover, not a blocker.
 testgraphical ⬜  (graphical/visual regression tests; needs instsetoo_native + 
qadevOOo)
 
 ── Remaining: Java-based ────────────────────────────────────────────────
diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl
index 34a15f1b89..861093d411 100644
--- a/build/rules/gtest_test.bzl
+++ b/build/rules/gtest_test.bzl
@@ -183,14 +183,66 @@ def _server_lines(server_exe, server_args, port):
         ":_portup",
     ]
 
+def _stage_data_dirs(ctx, d, data_dirs):
+    """Stage whole fixture DIRECTORIES, preserving their internal shape.
+
+    data_tree maps one label to one staged path, which is right when the exact
+    location of each file is the thing under test (a mini UNO installation has
+    two files named bootstrap.ini and only their directories tell them apart).
+    It is the wrong shape for a fixture that is simply "this directory of test
+    documents": the qadevOOo unoapi runner takes a document ROOT (-tdoc) and
+    joins a name onto it at runtime, so the build cannot know which documents a
+    scenario will open — the whole directory has to be there.
+
+    Each entry is {label: "staged/dir"}, and every file the label provides is
+    staged under it at its path relative to the LONGEST COMMON DIRECTORY of 
that
+    label's files.  Deriving the strip prefix instead of taking one as an
+    argument keeps the call site to the one fact it actually knows (where the
+    tree goes), and is exact for the usual `glob(["<dir>/**"])` filegroup: the
+    common directory IS <dir>, including when it holds only one file.  A glob
+    that reaches outside its directory shortens the common prefix and so stages
+    deeper than intended, which is visible in the staged layout rather than
+    silent.
+
+    Source files only, in practice: a mix of source and generated inputs shares
+    no directory prefix at all (bazel-out/... vs the execroot), which the same
+    rule would faithfully — and uselessly — reflect.
+    """
+    staged = []
+    for label, rel in data_dirs.items():
+        files = label[DefaultInfo].files.to_list()
+        if not files:
+            fail("data_dirs entry %s -> %s: the label provides no files." %
+                 (label.label, rel))
+
+        common = files[0].dirname.split("/")
+        for f in files[1:]:
+            parts = f.dirname.split("/")
+            n = 0
+            for i in range(min(len(common), len(parts))):
+                if common[i] != parts[i]:
+                    break
+                n = i + 1
+            common = common[:n]
+        prefix = "/".join(common)
+
+        for f in files:
+            sub = f.path[len(prefix) + 1:] if prefix else f.path
+            o = ctx.actions.declare_file(d + "/" + rel + "/" + sub)
+            ctx.actions.symlink(output = o, target_file = f)
+            staged.append(o)
+    return staged
+
 # ── shared with junit_test.bzl ───────────────────────────────────────────────
-# The Java/UNO tests emit a launcher .bat of their own and need exactly these 
two
+# The Java/UNO tests emit a launcher .bat of their own and need exactly these
 # primitives: a %~dp0-relative path to the staged install (bazel test's working
-# directory is neither the execroot nor the exe's directory), and the escaping
-# rules for a value baked into a .bat (the percent landmine above).  
Re-exported
-# rather than copied, so both launchers keep one definition of each.
+# directory is neither the execroot nor the exe's directory), the escaping 
rules
+# for a value baked into a .bat (the percent landmine above), and directory
+# staging.  Re-exported rather than copied, so both launchers keep one
+# definition of each.
 launcher_relpath = _windows_relpath
 launcher_expand_tokens = _expand_tokens
+stage_data_dirs = _stage_data_dirs
 
 def _staged_gtest_test_impl(ctx):
     # The staging dir is normally "<name>.run".  bin_layout makes it
diff --git a/build/rules/junit_test.bzl b/build/rules/junit_test.bzl
index eaf297802f..1635644f1f 100644
--- a/build/rules/junit_test.bzl
+++ b/build/rules/junit_test.bzl
@@ -43,7 +43,7 @@
 load("@rules_java//java:defs.bzl", "java_library")
 load("@rules_java//java/common:java_info.bzl", "JavaInfo")
 load("//build:jre.bzl", "jre_home_native", "jre_java_exe")
-load(":gtest_test.bzl", "launcher_expand_tokens", "launcher_relpath")
+load(":gtest_test.bzl", "launcher_expand_tokens", "launcher_relpath", 
"stage_data_dirs")
 
 _JUNIT = "//build/third_party/junit:junit"
 _RUNNER = "org.junit.runner.JUnitCore"
@@ -99,6 +99,11 @@ def _uno_junit_test_impl(ctx):
         ctx.actions.symlink(output = o, target_file = files[0])
         staged.append(o)
 
+    # Whole fixture DIRECTORIES.  The unoapi runner takes a document root and
+    # joins names onto it at run time, so which documents a scenario opens is 
not
+    # a build-time fact — the directory goes over as a directory.
+    staged += stage_data_dirs(ctx, d, ctx.attr.data_dirs)
+
     # ── the staged office ───────────────────────────────────────────────────
     uno_program_dir = None
     for f in ctx.files.uno_install:
@@ -280,6 +285,7 @@ _uno_junit_test = rule(
         "jvm_flags": attr.string_list(),
         "uno_install": attr.label(allow_files = True, mandatory = True),
         "data_tree": attr.label_keyed_string_dict(allow_files = True),
+        "data_dirs": attr.label_keyed_string_dict(allow_files = True),
         "java_exe": attr.string(mandatory = True),
         "java_home": attr.string(mandatory = True),
         "fixture_starts_office": attr.bool(default = False),
@@ -293,6 +299,7 @@ def uno_junit_test(
         deps = [],
         uno_install = "//main/staging:install",
         data_tree = {},
+        data_dirs = {},
         test_args = {},
         jvm_flags = [],
         fixture_starts_office = False,
@@ -319,6 +326,13 @@ def uno_junit_test(
     its working directory set to the staged dir, so a suite that does
     new File("test_documents", …) finds them.
 
+    data_dirs: {label: "relative/staged/dir"} fixture DIRECTORIES — every file
+    the label provides, staged under that directory keeping its shape (the
+    strip prefix is their longest common directory, so a
+    glob(["testdocuments/**"]) filegroup lands as testdocuments/…). Use this 
for
+    a document root the test names as a whole, such as the unoapi runner's
+    -tdoc; use data_tree when the exact placement of one file is the point.
+
     fixture_starts_office: the LAUNCHER starts and kills the office, and the
     test attaches to it (`connect:`) instead of launching it (`path:`). Use 
this
     only for a suite that leaves the office unable to shut down, which 
otherwise
@@ -348,8 +362,107 @@ def uno_junit_test(
         fixture_starts_office = fixture_starts_office,
         uno_install = uno_install,
         data_tree = data_tree,
+        data_dirs = data_dirs,
         java_exe = jre_java_exe(),
         java_home = jre_home_native(),
         size = size,
         **kwargs
     )
+
+# The six runtime jars every qa/unoapi adapter needs.  Upstream's makefiles 
vary
+# — some list unoil.jar and jurt.jar, some do not — but the variation is
+# accidental: the ones that omit them still run because gbuild puts the whole
+# solver bin on the classpath.  The Runner needs all six regardless (unoil is
+# where every com.sun.star.* office API type lives).
+_UNOAPI_DEPS = [
+    "//main/qadevOOo:OOoRunner",
+    "//main/test:test_jar",
+    "//main/ridljar:ridl",
+    "//main/unoil:unoil",
+    "//main/jurt:jurt",
+    "//main/javaunohelper:juh_jar",
+]
+
+def unoapi_test(
+        name,
+        module,
+        sce,
+        xcl = None,
+        ini = None,
+        tdoc = None,
+        srcs = None,
+        deps = [],
+        test_args = {},
+        data_tree = {},
+        data_dirs = {},
+        **kwargs):
+    """One module's qa/unoapi suite — the qadevOOo UNOAPI runner over a live 
office.
+
+    These 18 suites are not hand-written tests. Each is the SAME three-line
+    adapter (connect, hand org.openoffice.Runner a scenario file and the
+    connection description, assert its boolean result); what differs between
+    modules is only the data — which objects the scenario names, which
+    individual interface tests are excluded, and where the fixture documents
+    live. So the whole category is one macro over that data, mirroring the
+    JunitTest_<module>_unoapi.mk files it ports, which differ only in their
+    `-Dorg.openoffice.test.arg.*` lines.
+
+    module: the source directory name, which is also the adapter's package —
+    org.openoffice.<module>.qa.unoapi.Test — for all 18 without exception.
+
+    sce: the scenario file (`-o <object>` per line, commented-out entries being
+    the objects a known issue broke). xcl: the matching exclusion list of
+    individual interface tests. ini: a properties file, used only by dbaccess.
+
+    Each is staged under its own basename and the argument points at the staged
+    copy, never at the source path: the arguments are read at RUN time, and
+    bazel test's working directory is neither the source tree nor the execroot.
+    These are NATIVE paths with single backslashes, deliberately —
+    org.openoffice.test.Argument.get is a bare System.getProperty, so none of
+    these values is macro-expanded and the doubling rtl::Bootstrap values need
+    (see gtest_test's `env`) would arrive here as literal double backslashes.
+    Staging by basename is also what makes the two adapters that IGNORE these
+    arguments work — chart2 and forms hardcode "sch.sce" / "forms.sce",
+    "knownissues.xcl" and "testdocuments" as paths relative to the working
+    directory instead of calling Argument.get.
+
+    tdoc: the module's test-document directory, staged whole (see data_dirs) as
+    "testdocuments". Leave it unset for a scenario whose objects build their
+    documents through SOfficeFactory and load nothing from disk; the argument
+    then points at the staged dir itself, which is what upstream's 
argument-less
+    modules effectively get.
+    """
+    files = {sce: sce.split("/")[-1]}
+    args = {"org.openoffice.test.arg.sce": "$(RUNDIR)\\" + files[sce]}
+    if xcl:
+        files[xcl] = xcl.split("/")[-1]
+        args["org.openoffice.test.arg.xcl"] = "$(RUNDIR)\\" + files[xcl]
+    if ini:
+        files[ini] = ini.split("/")[-1]
+        args["org.openoffice.test.arg.ini"] = "$(RUNDIR)\\" + files[ini]
+
+    dirs = dict(data_dirs)
+    if tdoc:
+        native.filegroup(
+            name = name + "_tdoc",
+            srcs = native.glob([tdoc + "/**"]),
+            testonly = True,
+        )
+        dirs[":" + name + "_tdoc"] = "testdocuments"
+        args["org.openoffice.test.arg.tdoc"] = "$(RUNDIR)\\testdocuments"
+    else:
+        args["org.openoffice.test.arg.tdoc"] = "$(RUNDIR)"
+
+    files.update(data_tree)
+    args.update(test_args)
+
+    uno_junit_test(
+        name = name,
+        srcs = srcs if srcs else ["qa/unoapi/Test.java"],
+        classes = ["org.openoffice.%s.qa.unoapi.Test" % module],
+        data_tree = files,
+        data_dirs = dirs,
+        test_args = args,
+        deps = deps + _UNOAPI_DEPS,
+        **kwargs
+    )
diff --git a/main/chart2/BUILD.bazel b/main/chart2/BUILD.bazel
index 5d6de66d1a..8dfcee5713 100644
--- a/main/chart2/BUILD.bazel
+++ b/main/chart2/BUILD.bazel
@@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rename_file", "rsc_res")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -449,3 +450,22 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 12 objects, none commented out — the chart model and its wrappers (Diagram,
+# Axis, DataSeries, Title, Legend, …).  chart2's adapter is one of the TWO that
+# ignore Argument.get and hardcode "sch.sce" / "knownissues.xcl" /
+# "testdocuments" as paths relative to the working directory; staging by
+# basename is what makes both spellings resolve to the same files.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "chart2",
+    sce = "qa/unoapi/sch.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/configmgr/BUILD.bazel b/main/configmgr/BUILD.bazel
index 0fe5d132b8..1763ab9201 100644
--- a/main/configmgr/BUILD.bazel
+++ b/main/configmgr/BUILD.bazel
@@ -3,6 +3,7 @@ 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")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -276,3 +277,19 @@ gtest_test(
     uno_install = "//main/staging:install",
 )
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# Two objects, configmgr.ConfigurationProvider and configmgr.DefaultProvider.
+# Nothing to do with configmgr/qa/unit, which is red on a retired bootstrap
+# mechanism (component_writeInfo): this one runs against a live office and only
+# asks the running configuration manager for its UNO interfaces.  No xcl and no
+# testdocuments — upstream's makefile defines the sce and nothing else.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "configmgr",
+    sce = "qa/unoapi/module.sce",
+)
diff --git a/main/dbaccess/BUILD.bazel b/main/dbaccess/BUILD.bazel
index f8c7d1efd8..cca3f646e7 100644
--- a/main/dbaccess/BUILD.bazel
+++ b/main/dbaccess/BUILD.bazel
@@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rename_file", "rsc_res")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -964,3 +965,26 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# The only adapter that takes -ini, and the reason is an EXTERNAL dependency:
+# dbaccess.props names a MySQL server (jdbc.url=mysql://unoapi:3306/testDB) 
that
+# no developer machine has.  The objects that reach for it — ORowSet,
+# OSingleSelectQueryComposer — will fail here; the rest (ODatabaseContext,
+# OInteractionHandler, the dialogs) do not touch it.  Wired anyway: a partial
+# red that names which objects need a database is a better record than an
+# unwired suite, and the file is staged so pointing it at a real server is a
+# one-line change.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "dbaccess",
+    sce = "qa/unoapi/dbaccess.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    ini = "qa/unoapi/dbaccess.props",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/extensions/BUILD.bazel b/main/extensions/BUILD.bazel
index 99d2d7571c..13f6e34aa6 100644
--- a/main/extensions/BUILD.bazel
+++ b/main/extensions/BUILD.bazel
@@ -9,6 +9,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 # ── Component XML files (for services.rdb via postprocess) 
────────────────────
 exports_files(glob([
@@ -765,3 +766,18 @@ cc_binary(
 # Deferred: mozbootstrap.uno — Linux/macOS only (skipped on Windows).
 # Deferred: activex/msidl, activex/main — ActiveX/COM tooling required.
 # ─────────────────────────────────────────────────────────────────────────────
+
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# One object, pcr.ObjectInspector — listed TWICE on purpose.  The comment in
+# extensions.sce explains it: the Runner only prints "N of M tests failed" when
+# it ran more than one test, and the harness above it parses that line.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "extensions",
+    sce = "qa/unoapi/extensions.sce",
+)
diff --git a/main/forms/BUILD.bazel b/main/forms/BUILD.bazel
index f16531f2af..1b93e43a82 100644
--- a/main/forms/BUILD.bazel
+++ b/main/forms/BUILD.bazel
@@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -157,3 +158,21 @@ rsc_res(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 34 objects, the largest of the control/model suites.  The second of the two
+# adapters that hardcode their paths instead of calling Argument.get (see
+# chart2).  testdocuments holds TestDB, a directory — which is why the fixture
+# has to go over as a directory tree and not as a list of files.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "forms",
+    sce = "qa/unoapi/forms.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/framework/BUILD.bazel b/main/framework/BUILD.bazel
index 4dc6dbb126..ab703565e8 100644
--- a/main/framework/BUILD.bazel
+++ b/main/framework/BUILD.bazel
@@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -562,3 +563,20 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 27 objects across the framework services — Desktop, Frame, the dispatch
+# providers, the URL transformer, type detection.  Its testdocuments carries 
the
+# two link documents and XTypeDetection.sxw that the detection objects open.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "framework",
+    sce = "qa/unoapi/framework.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/linguistic/BUILD.bazel b/main/linguistic/BUILD.bazel
index 6a53ef672f..d5310af984 100644
--- a/main/linguistic/BUILD.bazel
+++ b/main/linguistic/BUILD.bazel
@@ -1,6 +1,7 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -96,3 +97,19 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# Two objects, lng.DicList and lng.LinguProps.  Upstream points -tdoc at
+# qa/unoapi ITSELF (there is no testdocuments subdirectory), which is a way of
+# passing something non-null rather than a real document root — neither object
+# opens a document.  Staged dir it is, same as svtools.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "linguistic",
+    sce = "qa/unoapi/lng.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+)
diff --git a/main/qadevOOo/BUILD.bazel b/main/qadevOOo/BUILD.bazel
index 6dc8f1ee4a..d43c634014 100644
--- a/main/qadevOOo/BUILD.bazel
+++ b/main/qadevOOo/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_java//java:defs.bzl", "java_library")
-load("//build/rules:junit_test.bzl", "uno_junit_test")
+load("//build/rules:junit_test.bzl", "uno_junit_test", "unoapi_test")
 
 # ── OOoRunner.jar ────────────────────────────────────────────────────────────
 # The qadevOOo test framework (lib.*, base.*, util.*, share.*, mod.* …) used 
as a
@@ -91,27 +91,12 @@ java_library(
 # which are what this target is for, then run and report.  What is given up is
 # tearDown's check that the office exits 0, which for this suite is exactly the
 # thing that is broken and would otherwise cost 300 s per run to re-observe.
-uno_junit_test(
+unoapi_test(
     name = "qa_unoapi",
-    srcs = ["qa/unoapi/Test.java"],
-    classes = ["org.openoffice.qadevOOo.qa.unoapi.Test"],
+    module = "qadevOOo",
+    sce = "qa/unoapi/qadevOOo.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
     fixture_starts_office = True,
-    data_tree = {
-        "qa/unoapi/qadevOOo.sce": "qadevOOo.sce",
-        "qa/unoapi/knownissues.xcl": "knownissues.xcl",
-    },
-    test_args = {
-        "org.openoffice.test.arg.sce": "$(RUNDIR)\\qadevOOo.sce",
-        "org.openoffice.test.arg.xcl": "$(RUNDIR)\\knownissues.xcl",
-    },
-    deps = [
-        ":OOoRunner",
-        "//main/test:test_jar",
-        "//main/ridljar:ridl",
-        "//main/unoil:unoil",
-        "//main/jurt:jurt",
-        "//main/javaunohelper:juh_jar",
-    ],
 )
 
 uno_junit_test(
diff --git a/main/sc/BUILD.bazel b/main/sc/BUILD.bazel
index 2623ee76fb..6e72582595 100644
--- a/main/sc/BUILD.bazel
+++ b/main/sc/BUILD.bazel
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_target")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -1277,3 +1278,21 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 43 active objects — and 67 commented out, each with the issue number that
+# broke it.  That ratio is the honest state of the Calc unoapi suite upstream,
+# not something lost in migration; the commented lines are left exactly as they
+# are so re-enabling one is a one-character edit.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "sc",
+    sce = "qa/unoapi/sc.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/sd/BUILD.bazel b/main/sd/BUILD.bazel
index 097e04782c..509ac17750 100644
--- a/main/sd/BUILD.bazel
+++ b/main/sd/BUILD.bazel
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_target")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -932,3 +933,18 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 12 objects — the draw pages, layers and presentation wrappers.  No
+# testdocuments: every object is built through SOfficeFactory from an empty
+# document, so upstream's makefile passes no -tdoc either.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "sd",
+    sce = "qa/unoapi/sd.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+)
diff --git a/main/sfx2/BUILD.bazel b/main/sfx2/BUILD.bazel
index ce4791aa23..eb38accf2f 100644
--- a/main/sfx2/BUILD.bazel
+++ b/main/sfx2/BUILD.bazel
@@ -4,6 +4,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_package", "sdi_target")
 load("//build/rules:gtest_test.bzl", "gtest_test")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -265,3 +266,19 @@ gtest_test(
     # enumerate it in runtime_dlls.
     uno_install = "//main/staging:install",
 )
+
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# Three objects (the standalone document-info object and the frame loaders),
+# with the .sdw/.stw documents they open staged beside them.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "sfx2",
+    sce = "qa/unoapi/sfx.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+)
diff --git a/main/starmath/BUILD.bazel b/main/starmath/BUILD.bazel
index 02a71517c3..bcc5ba1e5d 100644
--- a/main/starmath/BUILD.bazel
+++ b/main/starmath/BUILD.bazel
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_target")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -272,3 +273,18 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 9 objects: the model, the two accessibles, and the XML import/export filters.
+# Target named qa_unoapi like every other module; upstream's makefile calls it
+# sm_unoapi after the library rather than the directory.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "starmath",
+    sce = "qa/unoapi/sm.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+)
diff --git a/main/svtools/BUILD.bazel b/main/svtools/BUILD.bazel
index 377b44e1ca..13718ed292 100644
--- a/main/svtools/BUILD.bazel
+++ b/main/svtools/BUILD.bazel
@@ -2,7 +2,7 @@ package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
-load("//build/rules:junit_test.bzl", "uno_junit_test")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -235,36 +235,15 @@ rsc_res(
 # entries are commented out with the issue number that broke them (#i110988,
 # #i88276, …), so this suite is exactly one object today:
 # svtools.AccessibleTabBar.  knownissues.xcl is the second half of that
-# mechanism — a list of individual interface tests to skip.  Both are staged
-# rather than passed as source paths, because the arguments are read at run 
time
-# and bazel test's working directory is neither the source tree nor the 
execroot.
-uno_junit_test(
+# mechanism — a list of individual interface tests to skip.
+#
+# No tdoc: AccessibleTabBar builds its document through SOfficeFactory and 
loads
+# nothing from disk, and svtools ships no qa/unoapi/testdocuments to stage.
+unoapi_test(
     name = "qa_unoapi",
-    srcs = ["qa/unoapi/Test.java"],
-    classes = ["org.openoffice.svtools.qa.unoapi.Test"],
-    data_tree = {
-        "qa/unoapi/svtools.sce": "svtools.sce",
-        "qa/unoapi/knownissues.xcl": "knownissues.xcl",
-    },
-    test_args = {
-        "org.openoffice.test.arg.sce": "$(RUNDIR)\\svtools.sce",
-        "org.openoffice.test.arg.xcl": "$(RUNDIR)\\knownissues.xcl",
-        # The runner's test-document root.  AccessibleTabBar builds its 
document
-        # through SOfficeFactory and loads nothing from disk, so no qadevOOo
-        # testdocs tree is staged here; a suite whose .sce names objects that 
DO
-        # open a fixture document will need one.  Upstream leaves this argument
-        # unset entirely (the makefile defines no DEFS), which is why its own
-        # Test.java would pass null.
-        "org.openoffice.test.arg.tdoc": "$(RUNDIR)",
-    },
-    deps = [
-        "//main/qadevOOo:OOoRunner",
-        "//main/test:test_jar",
-        "//main/ridljar:ridl",
-        "//main/unoil:unoil",
-        "//main/jurt:jurt",
-        "//main/javaunohelper:juh_jar",
-    ],
+    module = "svtools",
+    sce = "qa/unoapi/svtools.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
 )
 
 exports_files(glob(["**/*.component"]))
diff --git a/main/svx/BUILD.bazel b/main/svx/BUILD.bazel
index 29cb72bc25..8e4eaac59f 100644
--- a/main/svx/BUILD.bazel
+++ b/main/svx/BUILD.bazel
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rename_file", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_package", "sdi_target")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -995,4 +996,20 @@ filegroup(
     visibility = ["//main/postprocess:__pkg__"],
 )
 
-exports_files(glob(["**/*.component"]))
\ No newline at end of file
+exports_files(glob(["**/*.component"]))
+
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 11 objects — the shape wrappers and the graphic-import path, which is why the
+# two JPEGs are in testdocuments alongside the .sxd.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "svx",
+    sce = "qa/unoapi/svx.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+)
diff --git a/main/sw/BUILD.bazel b/main/sw/BUILD.bazel
index bf76cb8804..d7a47133ff 100644
--- a/main/sw/BUILD.bazel
+++ b/main/sw/BUILD.bazel
@@ -4,6 +4,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 load("//build/rules:rsc_pipeline.bzl", "rsc_res")
 load("//build/rules:sdi_target.bzl", "sdi_target")
 load("//build/rules:gtest_test.bzl", "gtest_test")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -1544,3 +1545,22 @@ gtest_test(
     # than enumerating the closure (see gtest_test's uno_install).
     uno_install = "//main/staging:install",
 )
+
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 75 objects, the largest unoapi scenario in the tree: the whole Writer text
+# model (paragraphs, portions, tables, fields, sections, bookmarks, the
+# embedded-object and mail-merge paths).  Expect this one to be the long pole 
of
+# the category — every object is instantiated in the running office.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "sw",
+    sce = "qa/unoapi/sw.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/test/readme.md b/main/test/readme.md
index 30f251e679..e69242d1ec 100644
--- a/main/test/readme.md
+++ b/main/test/readme.md
@@ -419,6 +419,9 @@ Each of these was invisible until a foreign process loaded 
our DLLs:
 | `//main/svtools:qa_unoapi` | the first UNOAPI suite ever run here — 26 
interface/property checks on `svtools.AccessibleTabBar` | ~25 s |
 | `//main/qadevOOo:qa_unoapi` | the runner testing itself 
(`qadevOOo.SelfTest`) — the guard on the framework the other unoapi suites are 
built out of. Needs `fixture_starts_office`, see below | ~16 s |
 
+The other 16 `qa/unoapi` suites are wired but have not been run yet — see
+[the category section](#the-whole-qaunoapi-category-all-18-wired) below.
+
 ### When the office will not shut down: `fixture_starts_office`
 
 `qadevOOo:qa_unoapi` is the suite that motivated this. With the faithful
@@ -463,19 +466,113 @@ It is **opt-in and costs something**: `tearDown`'s check 
that the office
 terminates cleanly and exits 0. Use it only where that check is the thing that
 is broken, and record why.
 
-### The rest of the category, and what each needs
-
-There are **18** `qa/unoapi` and **24** `qa/complex` directories in the tree, 
so
-this is a queue, not a leftover. Two things gate the remainder:
+### The whole `qa/unoapi` category: all 18 wired
+
+Every `qa/unoapi` directory in the tree is now a target, all named
+`//main/<module>:qa_unoapi`. They are not 18 different tests — they are 18 sets
+of *data* for one runner, which is why they are one macro, `unoapi_test`
+(`build/rules/junit_test.bzl`), over the four things that actually differ:
+
+| | |
+| --- | --- |
+| `module` | the source directory name, which is also the adapter's package — 
`org.openoffice.<module>.qa.unoapi.Test` for all 18 without exception |
+| `sce` | the scenario: one `-o <object>` per line, commented-out lines 
carrying the issue number that broke that object |
+| `xcl` | the matching exclusion list of individual interface tests |
+| `tdoc` | the module's `qa/unoapi/testdocuments` directory, when it has one |
+
+That mirrors the `JunitTest_<module>_unoapi.mk` files being ported, which 
differ
+only in their `-Dorg.openoffice.test.arg.*` lines.
+
+**`-tdoc` is per-module, and an earlier note here had it wrong.** It is *not*
+`qadevOOo/testdocs` — every makefile that sets it points at
+`$(SRCDIR)/<module>/qa/unoapi/testdocuments`, 10 small directories totalling
+about 570 KB (`linguistic` is the odd one, pointing at `qa/unoapi` itself, 
which
+is a way of passing something non-null: neither of its two objects opens a
+document). `qadevOOo/testdocs` is only the *fallback* `util.utils`
+`getFullTestDocName` reaches for when the argument is absent and `SRC_ROOT` is
+set — a dmake-tree assumption, and not a path any wired suite takes.
+
+The gate this did need was **`data_dirs`**, the directory-preserving companion
+to `data_tree`, now in `gtest_test.bzl` and shared by both launchers. 
`data_tree`
+maps one label to one staged path, which is right when the exact placement of a
+file is the thing under test (a mini UNO installation has two files named
+`bootstrap.ini` and only their directories tell them apart). It is the wrong
+shape for a document *root*: the runner joins a document name onto `-tdoc` at
+run time, so which documents a scenario opens is not a build-time fact and the
+directory has to go over whole — including `dbaccess`'s and `forms`'s
+`testdocuments/TestDB/`, a nested directory `data_tree` cannot express at all.
+Each entry stages every file the label provides under the chosen directory, at
+its path relative to the **longest common directory** of that label's files; 
the
+strip prefix is derived rather than declared, which is exact for the
+`glob(["<dir>/**"])` filegroup the macro generates and keeps the call site to 
the
+one fact it knows — where the tree goes.
+
+Expect reds, and expect them to be informative rather than structural — this is
+the first time these suites have run here at all.
+
+#### First one run: `toolkit`, red on an **orphaned mutex in `acc.dll`**
+
+The fixture works — it connects, stages `testdocuments/`, and the first object
+(`toolkit.AccessibleDropDownComboBox`, the Find toolbar's combo box) passes 
four
+whole interfaces. Then the office **hangs**, and takes the other 52 objects 
with
+it. Diagnosed with a non-invasive `cdb -pv -p <pid>` against the live hang:
+
+- Windows logs an **Application Hang** (event 1002), *not* a crash; the office
+  stops pumping messages and `Process.Responding` is `False`. The Java side 
sees
+  only the consequence — `EOFException` on the URP pipe, then 
`DisposedException`
+  out of `tearDown`.
+- The **main thread holds the SolarMutex** (inside a WinProc dispatch under
+  `Application::Execute` → `Yield` → `DispatchMessageW`) delivering a focus
+  event: `Window::ImplGrabFocus` → `ImplCallActivateListeners` →
+  `VclEventListeners::Call` → 
`ootk!VCLXAccessibleComponent::WindowEventListener`
+  → `acc.dll` → `sal3!osl_acquireMutex`, where it blocks.
+- A **`binaryurp` worker** — the test's incoming UNO call — blocks on the 
*same*
+  `acc.dll` mutex.
+- `!locks` names the contended critical section (`LockCount 2`, exactly those 
two
+  waiters) and its `OwningThread` — **and that thread ID is not in the live
+  thread list.**
+
+So it is *not* a lock-order inversion, and not the solar-mutex-vs-URP shutdown
+deadlock recorded above for `qadevOOo`: the lock is **orphaned**. A thread left
+`acc.dll`'s mutex held and exited, and an osl mutex is a plain Windows
+`CRITICAL_SECTION` — once orphaned it can never be acquired again, so the first
+VCL event *and* the first UNO call that reach accessibility both block forever.
+Nothing recovers it in-process.
+
+That is a real product defect reachable by any assistive technology that 
attaches
+to a running office, not just by this test, and fixing it is a source change in
+`main/accessibility` — out of scope here. The suite stays wired and red, the 
same
+disposition as `//main/bridges:test_any_jni`. Upstream's `knownissues.xcl`
+excludes `toolkit.AccessibleComboBox` but **not** `AccessibleDropDownComboBox`,
+i.e. upstream considers this object testable — consistent with a `qa/` 
directory
+gated behind `ENABLE_UNIT_TESTS=NO` for a decade that has simply never run.
+
+Two more reds are known in advance:
+
+- **`dbaccess`** is the only adapter taking `-ini`, and its `dbaccess.props`
+  names a MySQL server (`jdbc.url=mysql://unoapi:3306/testDB`) no developer
+  machine has. `ORowSet` and `OSingleSelectQueryComposer` reach for it; the
+  other seven objects do not. Wired anyway — a partial red that names which
+  objects need a database beats an unwired suite, and the props file is staged,
+  so pointing it at a real server is a one-line change.
+- **`sc`** has 43 active objects and **67 commented out**, each with its issue
+  number. That ratio is the honest upstream state of the Calc suite, not
+  something lost in migration; the lines are left exactly as they are.
+
+The long poles are `sw` (75 objects), `toolkit` (53), `sc` (43) and `forms`
+(34) — every object is instantiated in a running office, so those four take
+`size = "large"`.
+
+### What is left
+
+That leaves the **24** `qa/complex` directories, which unlike unoapi are
+hand-written suites with no shared shape, so they come one at a time. Two 
things
+gate them:
 
-- **`-tdoc`** — 11 of the 18 unoapi adapters pass a test-document root, which
-  means staging `qadevOOo/testdocs` (53 entries, some of them directories). The
-  rule's `data_tree` stages one *file* per entry; a directory-preserving
-  variant is the next piece of rule work. `svtools`' scenario builds its
-  document through `SOfficeFactory` and needs none, which is why it went first.
 - **per-module fixtures** — `svl/qa/complex/ConfigItems` needs a C++ helper
-  component built alongside; `writerfilter/qa/complex` and several others have
-  no `makefile.mk` at all upstream, i.e. they were never wired there either.
+  component built alongside;
+- **several were never wired upstream either** — `writerfilter/qa/complex` and
+  others have no `makefile.mk` at all.
 
 ## The sal suite is deliberately NOT a green gate
 
diff --git a/main/toolkit/BUILD.bazel b/main/toolkit/BUILD.bazel
index e5b06cb0a5..264c1943a6 100644
--- a/main/toolkit/BUILD.bazel
+++ b/main/toolkit/BUILD.bazel
@@ -1,6 +1,7 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -131,3 +132,60 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 53 objects: every UnoControl* pair (control + model) plus the AWT peers.  The
+# single fixture document is poliball.gif, loaded by the image-control tests.
+#
+# RED, AND LEFT RED — an ORPHANED MUTEX IN acc.dll HANGS THE OFFICE.  The 
fixture
+# itself is fine: the suite connects, and the FIRST object
+# (toolkit.AccessibleDropDownComboBox, the Find toolbar's combo box) passes 
four
+# whole interfaces — XAccessibleExtendedComponent, XAccessibleEventBroadcaster,
+# XAccessibleAction and the rest — before anything goes wrong.  It dies on the
+# NEXT object creation, and takes all 53 with it.
+#
+# Diagnosed, not guessed (`cdb -pv -p <pid>`, non-invasive, against the live
+# hang):
+#
+#  * Windows logs an "Application Hang" (event 1002), NOT a crash — the office
+#    stops pumping messages and is killed; Process.Responding is False.  What 
the
+#    Java side sees is only the consequence: an EOFException on the URP pipe,
+#    then DisposedException out of tearDown.
+#  * the office's MAIN thread holds the SolarMutex (it is inside a WinProc
+#    dispatch under Application::Execute -> Yield -> DispatchMessageW) 
delivering
+#    a focus event: Window::ImplGrabFocus -> ImplCallActivateListeners ->
+#    VclEventListeners::Call -> 
ootk!VCLXAccessibleComponent::WindowEventListener
+#    -> acc.dll -> sal3!osl_acquireMutex, where it BLOCKS.
+#  * a binaryurp worker — the test's incoming UNO call — blocks in the SAME
+#    acc.dll mutex, one frame below binaryurp.
+#  * `!locks` names the contended critical section (LockCount 2, i.e. exactly
+#    those two waiters) and its OwningThread, and that thread ID IS NOT IN THE
+#    LIVE THREAD LIST.
+#
+# So this is not a lock-ORDER inversion and not the solar-mutex-vs-URP shutdown
+# deadlock already recorded for qadevOOo (see main/test/readme.md): the lock is
+# ORPHANED.  Some thread left acc.dll's mutex held and exited, after which an 
osl
+# mutex — a plain Windows CRITICAL_SECTION — can never be acquired again, so 
the
+# first VCL event and the first UNO call that reach accessibility both block
+# forever.  Nothing can recover it in-process.
+#
+# Fixing that is a SOURCE change in main/accessibility, which this branch does
+# not make, so the suite stays wired and red: it is an accurate report of a 
real
+# product defect, and the same defect is reachable by any assistive technology
+# that attaches to a running office, not just by this test.  Upstream's own
+# knownissues.xcl excludes toolkit.AccessibleComboBox but NOT
+# AccessibleDropDownComboBox, i.e. upstream considers this object testable —
+# consistent with a qa/ dir that has been gated behind ENABLE_UNIT_TESTS=NO 
for a
+# decade and has simply not run.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "toolkit",
+    sce = "qa/unoapi/toolkit.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+    size = "large",
+)
diff --git a/main/ucb/BUILD.bazel b/main/ucb/BUILD.bazel
index 45c1cfcccc..7676ef4319 100644
--- a/main/ucb/BUILD.bazel
+++ b/main/ucb/BUILD.bazel
@@ -1,6 +1,7 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -458,3 +459,19 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 10 objects — the broker itself plus one content provider per scheme (file,
+# hierarchy, package, help, FTP).  ucpftp.FTPContentProvider is the one to 
watch:
+# it is instantiated, not connected to anything, but it is the only object here
+# that names a network protocol.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "ucb",
+    sce = "qa/unoapi/ucb.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+)
diff --git a/main/xmloff/BUILD.bazel b/main/xmloff/BUILD.bazel
index c80cd1c419..2b464f7459 100644
--- a/main/xmloff/BUILD.bazel
+++ b/main/xmloff/BUILD.bazel
@@ -1,6 +1,7 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
+load("//build/rules:junit_test.bzl", "unoapi_test")
 
 _DEFINES = [
     "WNT", "GUI", "WIN32",
@@ -163,3 +164,18 @@ filegroup(
 
 exports_files(glob(["**/*.component"]))
 
+# ── qa/unoapi ────────────────────────────────────────────────────────────────
+# The qadevOOo UNOAPI runner over a live office: Test.java hands
+# org.openoffice.Runner the scenario below, and the Runner instantiates each
+# named object in the running office and checks every interface and property it
+# declares against the UNO type description.  See build/rules/junit_test.bzl.
+#
+# 10 objects, the XML import/export filter services.  emptyChart.sds is the one
+# fixture document — the chart filters need a chart to round-trip.
+unoapi_test(
+    name = "qa_unoapi",
+    module = "xmloff",
+    sce = "qa/unoapi/xmloff.sce",
+    xcl = "qa/unoapi/knownissues.xcl",
+    tdoc = "qa/unoapi/testdocuments",
+)

Reply via email to