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 ae2638cd9e6462aaeabd5ca6a261b6ea4c91ed2a Author: Peter Kovacs <[email protected]> AuthorDate: Sat Aug 1 17:26:06 2026 +0200 test(svl): first subsequent test — in-process UNO bootstrap via URE_BOOTSTRAP //main/svl:svl_qa_test_URIHelper (ports GoogleTest_svl_urihelper.mk) is the first test on Bazel that needs a real UNO installation rather than a set of co-located DLLs: its fixture calls cppu::defaultBootstrap_InitialComponentContext(). The frontier carried "needs the OfficeConnection / running-soffice fixture" as a single blocker. It is TWO, and conflating them made the work look bigger than it is: (a) in-process bootstrap — the test builds its own component context, NO soffice process is involved. This commit. (b) test::OfficeConnection — launches a real soffice and resolves a context over URP. Still unwired; test.dll is built and its args already come from rtl::Bootstrap (arg-soffice=path:…, arg-user=…), so what is missing is the process lifecycle, not the plumbing. (a) is now a `uno_install` attribute on gtest_test, pointed at //main/staging:install. The launcher exports ONE variable — URE_BOOTSTRAP=vnd.sun.star.pathname:<staged>/program/fundamental.ini — rather than reproducing the dmake recipe's hand-set UNO_TYPES / UNO_SERVICES / URE_INTERNAL_LIB_DIR (cppuhelper/qa/propertysetmixin/makefile.mk). fundamental.ini resolves ${ORIGIN} against its OWN directory, so pointing at it supplies all of them transitively, and they cannot drift when the ini changes. vnd.sun.star.pathname: takes a native path after the scheme, not a file URL (sal's resolvePathnameUrl → getFileURLFromSystemPath). The launcher also cd's into program/ and prepends it to PATH so the component DLLs named in services.rdb load. Caveat recorded at the use site and in the rule docs: such a test depends on the ENTIRE staged install, so it is slow to build and is not a unit test in any meaningful sense. Only reach for uno_install where UNO is genuinely bootstrapped. This particular test is also a thin proof — its heavier case testNormalizedMakeRelative, the one that drives the UCB, is compiled out upstream behind `#define RUN_OLD_FAILING_TESTS 0`, so only testFindFirstURLInText runs. Landmine found on the way, now documented for any future launcher: `bazel test` runs tests with a working directory that is NEITHER the exe's directory NOR the execroot. The first launcher built its paths from %CD% and failed with "The system cannot find the path specified" despite those paths being correct relative to the execroot. Launchers must locate everything from %~dp0 via a relative path computed at analysis time (_windows_relpath), which is also what makes uno_install work from any package depth. Whole test layer re-run: 18/18 green, no regressions from the launcher change. Co-Authored-By: Claude Opus 5 <[email protected]> --- CLAUDE.md | 23 ++++++++--- build/rules/gtest_test.bzl | 97 +++++++++++++++++++++++++++++++++++++--------- main/svl/BUILD.bazel | 53 +++++++++++++++++++++++++ main/test/readme.md | 51 ++++++++++++++++++++---- 4 files changed, 194 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1e11722886..56abcffa12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,11 +65,24 @@ test 🔨 C++ unit-test infra runnable — NOW THE FRONT-LINE TASK: br genuinely left is not standalone — svl/qa/complex + svtools/qa/unoapi + sfx2 + writerfilter/qa/complex are Java/UNO; svl/qa/test_URIHelper, configmgr/qa/unit and cppuhelper/qa/propertysetmixin bootstrap a UNO - component context. So the front line is now ONE fixture: - OfficeConnection (running-soffice). test.dll is built and its - arg plumbing is rtl::Bootstrap "arg-soffice=path:…"/"arg-user=…", - so the missing piece is a rule that points it at //main/staging:install - and gives the test exe its own UNO bootstrap. + component context. KEY DISTINCTION (was conflated under + "OfficeConnection", making the work look bigger than it is): those + are TWO fixtures. (a) IN-PROCESS bootstrap — + defaultBootstrap_InitialComponentContext(), NO soffice process — + is DONE: gtest_test's uno_install=//main/staging:install exports + URE_BOOTSTRAP at the staged program/fundamental.ini, whose ${ORIGIN} + then supplies UNO_TYPES/UNO_SERVICES/URE_INTERNAL_LIB_DIR + transitively (no hand-set env vars, no drift). First green: + //main/svl:svl_qa_test_URIHelper. Caveat — it depends on the WHOLE + install, so it is slow and not a unit test; only use uno_install + where UNO is genuinely bootstrapped. (b) test::OfficeConnection + (launch soffice -accept=…;urp, resolve over URP) is STILL UNWIRED + and is the remaining front line — test.dll is built and its args + come from rtl::Bootstrap (arg-soffice=path:…, arg-user=…), so what + is missing is the process lifecycle, not the plumbing. + LANDMINE for any new launcher: bazel test's CWD is NEITHER the exe + dir NOR the execroot — locate everything from %~dp0 (see + _windows_relpath in gtest_test.bzl). testtools ⬜ (bridgetest — pure-C++ UNO bridge round-trip; cli/pyuno/java variants need rules_java — see Java bucket) qadevOOo 🔨 OOoRunner.jar built (//main/qadevOOo:OOoRunner — qadevOOo QA diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl index 9217f857be..2f3b6eacf6 100644 --- a/build/rules/gtest_test.bzl +++ b/build/rules/gtest_test.bzl @@ -42,6 +42,23 @@ _APP_MANIFEST = "//main/external/msvcp90:vc90_app_manifest" # msvcr90 loose → R6034 → DllMain fails → exit 0xC0000142. _APP_MANIFEST_RES = "//main/external/msvcp90:vc90_app_manifest_res" +def _windows_relpath(from_dir, to_dir): + """Backslash relative path from one execroot-relative dir to another. + + Used to make the launcher .bat self-locating via %~dp0 instead of trusting + the working directory: `bazel test` does NOT run tests with the working + directory set to the execroot, so a %CD%-relative path to the staged install + resolves to nothing ("The system cannot find the path specified"). + """ + f = from_dir.split("/") + t = to_dir.split("/") + common = 0 + for i in range(min(len(f), len(t))): + if f[i] != t[i]: + break + common = i + 1 + return "..\\" * (len(f) - common) + "\\".join(t[common:]) + def _staged_gtest_test_impl(ctx): d = ctx.label.name + ".run" staged = [] @@ -75,32 +92,67 @@ def _staged_gtest_test_impl(ctx): ctx.actions.symlink(output = man, target_file = ctx.file.app_manifest) staged.append(man) - # Co-locating a data file with the exe is not enough for a test that opens it - # by bare relative name: `bazel test` runs the executable with the working - # directory set to the execroot, not to the exe's directory (the loader finds - # the staged DLLs via the exe's own path, which is why those work regardless). - # When run_in_staged_dir is set, hand Bazel a .bat that cd's into the staged - # dir first and forwards the exit code, so relative paths resolve there. + # ── UNO environment (subsequent / in-process-bootstrap tests) ──────────── + # A test that calls cppu::defaultBootstrap_InitialComponentContext() needs a + # real UNO installation: type + service rdbs, and every component DLL named + # in services.rdb. Rather than reinvent that, point it at the staged office + # via URE_BOOTSTRAP, the documented override for "which fundamental.ini + # describes this installation". fundamental.ini resolves ${ORIGIN} against + # its OWN directory, so that one variable transitively supplies UNO_TYPES, + # UNO_SERVICES, URE_INTERNAL_LIB_DIR and BRAND_BASE_DIR — no need to + # duplicate any of them here, and no drift when the ini changes. + # + # The install root is a fixed bazel-out path (tree_install declares its + # outputs in //main/staging), so locating program/fundamental.ini among the + # install files at analysis time yields the execroot-relative program dir. + # `bazel test` runs with the working directory set to the execroot, hence + # %CD% below. + uno_program_dir = None + for f in ctx.files.uno_install: + if f.path.endswith("/program/fundamental.ini"): + uno_program_dir = f.dirname + break + if ctx.files.uno_install and uno_program_dir == None: + fail("uno_install does not contain program/fundamental.ini — is it //main/staging:install?") + executable = staged_exe - if ctx.attr.run_in_staged_dir: - launcher = ctx.actions.declare_file(d + "/" + ctx.label.name + "_run.bat") - ctx.actions.write( - output = launcher, - content = "\r\n".join([ - "@echo off", + if ctx.attr.run_in_staged_dir or uno_program_dir: + launcher_dir = staged_exe.dirname # the .bat sits beside the staged exe + lines = ["@echo off", "setlocal"] + if uno_program_dir: + lines += [ + 'set "_EXE=%~dp0' + staged_exe.basename + '"', + # Resolved from the launcher's own location (%~dp0), not %CD%. + 'for %%I in ("%~dp0' + _windows_relpath(launcher_dir, uno_program_dir) + + '") do set "_PROG=%%~fI"', + # vnd.sun.star.pathname: takes a native path, not a file URL. + 'set "URE_BOOTSTRAP=vnd.sun.star.pathname:%_PROG%\\fundamental.ini"', + # Component DLLs named in services.rdb are loaded at run time and + # live in program/; the exe's own directory still wins for what it + # imports directly, so its staged copies are unaffected. + 'set "PATH=%_PROG%;%PATH%"', + 'cd /d "%_PROG%" || exit /b 1', + '"%_EXE%" %*', + ] + else: + # Co-locating a data file with the exe is not enough for a test that + # opens it by bare relative name: the working directory is the + # execroot, not the exe's directory (the loader finds the staged DLLs + # via the exe's own path, which is why those work regardless). + lines += [ 'cd /d "%~dp0" || exit /b 1', '"%~dp0' + staged_exe.basename + '" %*', - "exit /b %ERRORLEVEL%", - "", - ]), - is_executable = True, - ) + ] + lines += ["exit /b %ERRORLEVEL%", ""] + + launcher = ctx.actions.declare_file(d + "/" + ctx.label.name + "_run.bat") + ctx.actions.write(output = launcher, content = "\r\n".join(lines), is_executable = True) staged.append(launcher) executable = launcher return [DefaultInfo( executable = executable, - runfiles = ctx.runfiles(files = staged), + runfiles = ctx.runfiles(files = staged + ctx.files.uno_install), files = depset([executable]), )] @@ -113,6 +165,7 @@ _staged_gtest_test = rule( "companions": attr.label_list(cfg = "target"), "app_manifest": attr.label(allow_single_file = True, default = _APP_MANIFEST), "run_in_staged_dir": attr.bool(default = False), + "uno_install": attr.label(allow_files = True), }, ) @@ -131,6 +184,7 @@ def gtest_test( defines = [], runtime_dlls = [], data_files = [], + uno_install = None, companions = [], additional_linker_inputs = [], linkopts = [], @@ -142,6 +196,12 @@ def gtest_test( They are staged beside the exe AND the test is launched with its working directory set to that staged dir, which co-location alone does not give you (see run_in_staged_dir in the staging rule). + + uno_install: for *subsequent* tests that call + cppu::defaultBootstrap_InitialComponentContext(). Pass + //main/staging:install; the test then runs against that staged office via + URE_BOOTSTRAP. Note this makes the test depend on the whole install, so it + is far from a unit test — keep it off targets that don't need UNO. """ cc_binary( name = name + "_bin", @@ -169,5 +229,6 @@ def gtest_test( runtime = runtime_dlls + data_files + [_CRT], companions = companions, run_in_staged_dir = bool(data_files), + uno_install = uno_install, size = size, ) diff --git a/main/svl/BUILD.bazel b/main/svl/BUILD.bazel index e6c39278f1..92ef5712a2 100644 --- a/main/svl/BUILD.bazel +++ b/main/svl/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:gtest_test.bzl", "gtest_test") _DEFINES = [ "WNT", "GUI", "WIN32", @@ -208,5 +209,57 @@ rsc_res( visibility = ["//visibility:public"], ) +# ── qa unit test ────────────────────────────────────────────────── +# URIHelper::FindFirstURLInText (qa/test_URIHelper.cxx). Ports +# GoogleTest_svl_urihelper.mk (cppu cppuhelper sal svl stl tl utl). +# +# This is the first *subsequent* test on Bazel: its fixture calls +# cppu::defaultBootstrap_InitialComponentContext(), so it needs a real UNO +# installation rather than just a set of co-located DLLs. uno_install points it +# at the staged office and the gtest_test launcher exports URE_BOOTSTRAP — see +# //build/rules:gtest_test.bzl. That makes this test depend on the ENTIRE +# install, so it is slow to build and is not a unit test in any real sense. +# +# It does NOT need a running soffice (that is test::OfficeConnection, still +# unwired): the component context is bootstrapped in-process. Note the file's +# heavier case, testNormalizedMakeRelative — the one that drives the UCB — is +# compiled out upstream behind `#define RUN_OLD_FAILING_TESTS 0`, so only +# testFindFirstURLInText actually runs. +gtest_test( + name = "svl_qa_test_URIHelper", + srcs = ["qa/test_URIHelper.cxx"], + copts = _COPTS + ["/Imain/svl/inc/svl"], + defines = _DEFINES, + deps = [ + ":svl_headers", + "//main/sal:sal_headers", + "//main/cppu:cppu_headers", + "//main/cppuhelper:cppuhelper_headers", + "//main/tools:tools_headers", + "//main/unotools:unotools_headers", + "//main/udkapi:udkapi_idl_headers", + "//main/offapi:offapi_idl_headers", + "//main/stlport:stlport", + "@boost.legacy//:boost.legacy", + ], + additional_linker_inputs = [ + "//main/sal:sal_implib", + "//main/cppu:cppu3_implib", + "//main/cppuhelper:cppuhelper_implib", + "//main/tools:tl_implib", + "//main/unotools:utl_implib", + ":svl_implib", + ], + linkopts = [ + "$(execpath //main/sal:sal_implib)", + "$(execpath //main/cppu:cppu3_implib)", + "$(execpath //main/cppuhelper:cppuhelper_implib)", + "$(execpath //main/tools:tl_implib)", + "$(execpath //main/unotools:utl_implib)", + "$(execpath :svl_implib)", + ], + uno_install = "//main/staging:install", +) + exports_files(glob(["**/*.component"])) diff --git a/main/test/readme.md b/main/test/readme.md index 7157875f45..b7aa4773d1 100644 --- a/main/test/readme.md +++ b/main/test/readme.md @@ -70,8 +70,38 @@ holdouts). This brings the test layer onto Bazel so suites run under import lib of every DLL whose symbols the test TU references directly (e.g. `cppu3_implib` for `uno_any_destruct`/`typelib_*` pulled in by an `Any` destructor or `getCppuType`). -2. **Subsequent / UNO tests** — use `test::OfficeConnection` to launch a real - soffice. Need the staged install wired as a fixture. **Not yet wired.** +2. **Subsequent / UNO tests** — need a real UNO installation, not just + co-located DLLs. These are **two different fixtures**, long conflated under + the one name "OfficeConnection": + + a. **In-process bootstrap** — the test calls + `cppu::defaultBootstrap_InitialComponentContext()` and builds its own + component context. **No soffice process is involved.** WIRED: pass + `uno_install = "//main/staging:install"` to `gtest_test`. + + The launcher exports a single **`URE_BOOTSTRAP`** pointing at the staged + `program/fundamental.ini`. That is the whole trick: the dmake recipe + (`cppuhelper/qa/propertysetmixin/makefile.mk`) sets `UNO_TYPES`, + `UNO_SERVICES`, `URE_INTERNAL_LIB_DIR`, … by hand, but `fundamental.ini` + resolves `${ORIGIN}` against *its own* directory, so pointing at it + supplies all of them transitively — and they cannot drift when the ini + changes. `URE_BOOTSTRAP` takes a `vnd.sun.star.pathname:` URL (a *native* + path after the scheme; `sal`'s `resolvePathnameUrl` converts it). + The launcher also `cd`s into `program/` and prepends it to `PATH` so the + component DLLs named in `services.rdb` load. + + Green: `//main/svl:svl_qa_test_URIHelper`. + + **Cost:** the test depends on the entire staged install, so it is slow to + build and is not a unit test in any meaningful sense. Do not reach for + `uno_install` unless the test genuinely bootstraps UNO. + + b. **Running-office connection** — `test::OfficeConnection` (libtest) starts + a real soffice with `-accept=pipe,name=…;urp` and resolves a remote + context over URP. **Still unwired.** `test.dll` is built and its arguments + come from `rtl::Bootstrap` (`arg-soffice=path:<soffice.exe>`, + `arg-user=<user installation>`), so what is missing is the process + lifecycle, not the plumbing. ## Gotchas (learned the hard way) @@ -113,11 +143,18 @@ holdouts). This brings the test layer onto Bazel so suites run under (Not yet handled: under `--compilation_mode=dbg` the exes link `/MDd` but this `.res` still carries the *release* CRT manifest.) -- **Staging a data file beside the exe is not enough — the working directory is - the execroot.** `bazel test` launches the test with its CWD set to the - execroot, *not* the exe's directory. Co-located DLLs still resolve (the loader - searches the exe's own path), which makes it easy to assume relative file - opens will too — they don't. A test that opens a fixture by bare relative name +- **Never assume the test's working directory.** It is *not* the exe's + directory, and it is *not* the execroot either — a launcher that built paths + from `%CD%` produced "The system cannot find the path specified" even though + those paths were correct relative to the execroot. Anything the launcher needs + to find must be located from **`%~dp0`** (the .bat's own directory) via a + relative path computed at analysis time — `_windows_relpath` in + [gtest_test.bzl](../../build/rules/gtest_test.bzl) does this, and it is why + `uno_install` works from any package depth. + +- **Staging a data file beside the exe is not enough.** Co-located DLLs resolve + regardless (the loader searches the exe's own path), which makes it easy to + assume relative file opens will too — they don't. A test that opens a fixture by bare relative name (`//main/shell:shell_qa_zip` → `simpledocument.odt`) throws file-not-found while passing when run by hand from the staged dir. Pass such inputs as `data_files`: they are staged beside the exe *and* the target
