From 75b7f1e5c9d94e0769132da84701c1d331bee2f8 Mon Sep 17 00:00:00 2001 From: Jhin Lee Date: Sun, 8 Nov 2026 15:11:20 -0500 Subject: [PATCH] Export an in-process LiteRT marker from the runtime bridge Consumers cannot tell C API 1.0 runtimes whose desktop hosts load the prebuilt libLiteRt from ones that compile LiteRT in (#111), because both report the same upstream API version. Every runtime now exports litert_lm_native_in_process_litert_abi_version(): 1 for v0.19+ builds, whose linkage the build now verifies on every platform, else 0. Also covers the #113 linkage-check test gap and the stale CPU options decision message. Refs #113 --- .../workflows/pr_release_qualification.yml | 4 +- docs/platform_strategy.md | 39 ++++++++++--- native/bridge/BUILD.bazel | 10 ++++ native/bridge/litert_lm_bridge.c | 8 +++ native/bridge/litert_lm_bridge.h | 6 ++ native/bridge/litert_lm_bridge_test.c | 10 ++++ tools/build_upstream_runtime.py | 47 +++++++++------ tools/litert_cpu_options_identifier_patch.py | 5 +- tools/litert_lm_finish_reason_smoke.py | 23 +++++++- tools/litert_lm_symbols.py | 11 ++++ tools/test_build_upstream_runtime.py | 58 ++++++++++++++----- tools/test_litert_lm_bridge.py | 10 +++- tools/test_litert_lm_finish_reason_smoke.py | 12 ++++ tools/test_litert_lm_symbols.py | 7 +++ 14 files changed, 199 insertions(+), 51 deletions(-) diff --git a/.github/workflows/pr_release_qualification.yml b/.github/workflows/pr_release_qualification.yml index 00eebe4..ff5c31e 100644 --- a/.github/workflows/pr_release_qualification.yml +++ b/.github/workflows/pr_release_qualification.yml @@ -213,7 +213,7 @@ jobs: (matrix.platform == 'macos' && matrix.arch == 'arm64' && runner.arch == 'ARM64') run: python3 tools/fetch_litert_lm_asr_smoke_assets.py --output-dir asr-smoke-assets - - name: Run Unix ASR real-model smoke and finish-reason ABI check + - name: Run Unix ASR real-model smoke and bridge ABI checks if: >- (matrix.platform == 'linux' && matrix.arch == 'x64') || (matrix.platform == 'macos' && matrix.arch == 'arm64' && runner.arch == 'ARM64') @@ -284,7 +284,7 @@ jobs: whisper a1b2c3d4_parakeet_ctc.litertlm --clear-model-name \ --enum-override decoder_type=1 --expect-engine-created - - name: Run Windows ASR real-model smoke and finish-reason ABI check + - name: Run Windows ASR real-model smoke and bridge ABI checks if: matrix.platform == 'windows' && matrix.arch == 'x64' shell: pwsh env: diff --git a/docs/platform_strategy.md b/docs/platform_strategy.md index 2c34d4c..d5899be 100644 --- a/docs/platform_strategy.md +++ b/docs/platform_strategy.md @@ -41,11 +41,30 @@ callbacks. LiteRT-LM 0.15+ opaque stream chunks are translated to that stable callback contract inside the bridge. Consumers can probe `stream_proxy_callback_abi_version` before registering callbacks. Runtimes built with the carried v0.18.0/v0.18.1 finish-reason patch also report why a conversation -stream ended; consumers probe `stream_proxy_finish_reason_abi_version`. Source-built -native runtimes use Bazel `--package_path` on Unix-like runners and copy the -bridge package into the extracted source tree on Windows to avoid Bazel's -Windows package-path parser, without patching upstream source files in the -repository. +stream ended; consumers probe `stream_proxy_finish_reason_abi_version`. + +`litert_lm_native_in_process_litert_abi_version()` reports how the runtime +reaches LiteRT: + +| Value | Meaning | +| --- | --- | +| 1 | LiteRT is compiled into the runtime binary, and the build verified that the binary imports no separate `libLiteRt`. LiteRT-LM's external and in-memory weight handoff to LiteRT is ABI-valid. | +| 0 | Not established. Builds before v0.19 report 0; their Linux and Windows runtimes for v0.16 to v0.18 load upstream's prebuilt `libLiteRt` (#111). | +| missing | The runtime predates the marker; treat it as 0. | + +Every source build from LiteRT-LM v0.19 reports 1 on every platform. The +export is additive and carries no other ABI change. It is a bridge export +because `litert_lm_get_api_version` reports upstream's C API version, which this +repository does not change. A later change to what the marker guarantees gets a +new value, so consumers accept only values they know. Releases up to and +including `v0.19.1-1` lack the export; `v0.19.1-1` already links LiteRT in on +Linux and Windows, so a consumer that must accept it identifies it some other +way, such as a pinned checksum. + +Source-built native runtimes use Bazel `--package_path` on Unix-like runners and +copy the bridge package into the extracted source tree on Windows to avoid +Bazel's Windows package-path parser, without patching upstream source files in +the repository. For LiteRT-LM 0.16+, the same runtime exports `litert_lm_asr_*` ABI version 1. It adapts upstream `AsrEngine`/`AsrSession` to bounded push PCM and @@ -177,10 +196,12 @@ opaque options struct (`SetExternalWeightScopedFile`, `SetWeightInMemoryMap`). The host is built against libstdc++ or the MSVC STL, while upstream's prebuilt `libLiteRt` carries its own libc++, so that write was not ABI-valid: Linux lost the weights and Windows crashed, which broke every bundle with external-weight -sections, such as EmbeddingGemma-2. The build fails if a v0.19+ desktop host -still imports `libLiteRt`. Bundles keep shipping the upstream `libLiteRt` and -`libLiteRtWebGpuAccelerator` files so the consumer layout is unchanged, but the -host loads neither. +sections, such as EmbeddingGemma-2. The build fails if a v0.19+ host on any +platform still imports `libLiteRt`, and those hosts report +`litert_lm_native_in_process_litert_abi_version()` 1, so consumers can refuse +external-weight bundles on older desktop runtimes. Bundles keep shipping the +upstream `libLiteRt` and `libLiteRtWebGpuAccelerator` files so the consumer +layout is unchanged, but the host loads neither. The rest of this section covers v0.16 to v0.18. diff --git a/native/bridge/BUILD.bazel b/native/bridge/BUILD.bazel index 01488d7..60a2870 100644 --- a/native/bridge/BUILD.bazel +++ b/native/bridge/BUILD.bazel @@ -75,6 +75,13 @@ config_setting( define_values = {"litert_lm_c_api_v1": "true"}, ) +# Set only when the build links LiteRT into the runtime and verifies that the +# runtime imports no separate libLiteRt. +config_setting( + name = "in_process_litert", + define_values = {"litert_lm_in_process_litert": "true"}, +) + # Restrict the new export policy to the Android C API 1.0 runtime link. config_setting( name = "android_c_api_v1", @@ -107,6 +114,9 @@ cc_library( }) + select({ ":c_api_v1": ["LITERT_LM_C_API_V1=1"], "//conditions:default": [], + }) + select({ + ":in_process_litert": ["LITERT_LM_IN_PROCESS_LITERT=1"], + "//conditions:default": [], }), linkopts = select({ "@platforms//os:android": ["-ldl"], diff --git a/native/bridge/litert_lm_bridge.c b/native/bridge/litert_lm_bridge.c index 8c7519c..66fa300 100644 --- a/native/bridge/litert_lm_bridge.c +++ b/native/bridge/litert_lm_bridge.c @@ -245,6 +245,14 @@ LITERT_LM_BRIDGE_EXPORT int32_t stream_proxy_has_allocation_failure( return atomic_load_explicit(&context->allocation_failure, memory_order_acquire); } +LITERT_LM_BRIDGE_EXPORT int32_t litert_lm_native_in_process_litert_abi_version(void) { +#if defined(LITERT_LM_IN_PROCESS_LITERT) + return 1; +#else + return 0; +#endif +} + LITERT_LM_BRIDGE_EXPORT void stream_proxy_delete(void *callback_data) { free(callback_data); } diff --git a/native/bridge/litert_lm_bridge.h b/native/bridge/litert_lm_bridge.h index 7bfb498..b377800 100644 --- a/native/bridge/litert_lm_bridge.h +++ b/native/bridge/litert_lm_bridge.h @@ -27,6 +27,12 @@ LITERT_LM_BRIDGE_EXPORT int32_t stream_proxy_allocation_failure_abi_version(void LITERT_LM_BRIDGE_EXPORT int32_t stream_proxy_has_allocation_failure( const void *callback_data); +// 1 means LiteRT is compiled into this runtime binary, which imports no +// separate libLiteRt, so LiteRT-LM's external and in-memory weight handoff to +// LiteRT is ABI-valid (#111). 0 means that is not established. A runtime that +// lacks this export predates the marker and must be treated as 0. +LITERT_LM_BRIDGE_EXPORT int32_t litert_lm_native_in_process_litert_abi_version(void); + #ifdef __cplusplus } #endif diff --git a/native/bridge/litert_lm_bridge_test.c b/native/bridge/litert_lm_bridge_test.c index 35fc77f..75e05f5 100644 --- a/native/bridge/litert_lm_bridge_test.c +++ b/native/bridge/litert_lm_bridge_test.c @@ -54,6 +54,7 @@ void stream_proxy_free_string(char *value); int32_t stream_proxy_callback_abi_version(void); int32_t stream_proxy_finish_reason_abi_version(void); int32_t stream_proxy_get_finish_reason(const void *callback_data); +int32_t litert_lm_native_in_process_litert_abi_version(void); typedef struct callback_result { char *chunk; @@ -207,6 +208,15 @@ static int check_finish_reasons(void) { } int main(void) { +#if defined(LITERT_LM_IN_PROCESS_LITERT) + if (litert_lm_native_in_process_litert_abi_version() != 1) { + return 8; + } +#else + if (litert_lm_native_in_process_litert_abi_version() != 0) { + return 8; + } +#endif #if defined(LITERT_LM_STREAM_CHUNK_API) if (stream_proxy_callback_abi_version() != 2) { return 9; diff --git a/tools/build_upstream_runtime.py b/tools/build_upstream_runtime.py index 601ef3a..ad51652 100644 --- a/tools/build_upstream_runtime.py +++ b/tools/build_upstream_runtime.py @@ -28,6 +28,7 @@ from litert_lm_symbols import ( has_asr_bridge, has_omni_asr_engine, + in_process_litert_abi_version, is_at_least, uses_c_api_v1, required_bridge_symbols, @@ -582,6 +583,8 @@ def build_runtime( command.append("--define=litert_lm_asr_omni_engine=true") if uses_c_api_v1(upstream_tag): command.append("--define=litert_lm_c_api_v1=true") + if in_process_litert_abi_version(upstream_tag) == 1: + command.append("--define=litert_lm_in_process_litert=true") if require_finish_reason_patch_decision(upstream_tag): command.append(FINISH_REASON_DEFINE) if platform == "macos": @@ -863,19 +866,25 @@ def validate_exported_symbols(output: Path, upstream_tag: str) -> None: print(f"Validated LiteRT-LM and bridge symbols in {output}", flush=True) -def validate_desktop_litert_linkage(output: Path, platform: str, upstream_tag: str) -> None: - """Fails if a v0.19+ desktop host still loads LiteRT from another binary.""" - if platform not in {"linux", "windows"} or not is_at_least(upstream_tag, (0, 19, 0)): +SHARED_LITERT_NAMES = frozenset({"liblitert.so", "liblitert.dll", "liblitert.dylib", "litert"}) + + +def validate_in_process_litert_linkage(output: Path, platform: str, upstream_tag: str) -> None: + """Fails if a runtime that reports in-process LiteRT loads it from another binary.""" + if in_process_litert_abi_version(upstream_tag) != 1: return - if platform == "linux": - imported = set(elf_needed_libraries(output)) + if platform in {"linux", "android"}: + imported = elf_needed_libraries(output) + elif platform == "windows": + imported = list(windows_runtime.read_pe(output).all_imports()) else: - imported = set(windows_runtime.read_pe(output).all_imports()) - shared = sorted(name for name in imported if name.lower() in {"liblitert.so", "liblitert.dll"}) + imported = macho_needed_libraries(output) + shared = sorted(name for name in imported if Path(name).name.lower() in SHARED_LITERT_NAMES) if shared: raise RuntimeError( - f"{output.name} imports {', '.join(shared)}; v0.19+ desktop hosts must " - "compile LiteRT in so external-weight options stay ABI-valid (#111)") + f"{output.name} imports {', '.join(shared)}; a runtime reporting " + "litert_lm_native_in_process_litert_abi_version() 1 must compile " + "LiteRT in so external-weight options stay ABI-valid (#111)") print(f"Validated in-process LiteRT linkage in {output}", flush=True) @@ -897,6 +906,14 @@ def validate_android_gpu_environment_teardown(output: Path, platform: str) -> No print(f"Validated WebGPU teardown ordering in {output}", flush=True) +def validate_runtime(output: Path, platform: str, upstream_tag: str) -> None: + validate_exported_symbols(output, upstream_tag) + validate_in_process_litert_linkage(output, platform, upstream_tag) + validate_android_global_visibility(output, platform) + validate_android_protobuf_isolation(output, platform, upstream_tag) + validate_android_gpu_environment_teardown(output, platform) + + def main() -> int: parser = argparse.ArgumentParser( description=f"Build the upstream {UPSTREAM_REPO} C runtime library." @@ -934,11 +951,7 @@ def main() -> int: args.jobs, upstream_ref, ) - validate_exported_symbols(output, args.upstream_tag) - validate_desktop_litert_linkage(output, args.platform, args.upstream_tag) - validate_android_global_visibility(output, args.platform) - validate_android_protobuf_isolation(output, args.platform, args.upstream_tag) - validate_android_gpu_environment_teardown(output, args.platform) + validate_runtime(output, args.platform, args.upstream_tag) stage_runtime(output, args.platform, args.arch) stage_runtime_dependencies(output, source_root, args.platform, args.arch) stage_runtime_overrides(args.upstream_tag, args.platform, args.arch) @@ -969,11 +982,7 @@ def main() -> int: args.jobs, upstream_ref, ) - validate_exported_symbols(output, args.upstream_tag) - validate_desktop_litert_linkage(output, args.platform, args.upstream_tag) - validate_android_global_visibility(output, args.platform) - validate_android_protobuf_isolation(output, args.platform, args.upstream_tag) - validate_android_gpu_environment_teardown(output, args.platform) + validate_runtime(output, args.platform, args.upstream_tag) stage_runtime(output, args.platform, args.arch) stage_runtime_dependencies(output, source_root, args.platform, args.arch) stage_runtime_overrides(args.upstream_tag, args.platform, args.arch) diff --git a/tools/litert_cpu_options_identifier_patch.py b/tools/litert_cpu_options_identifier_patch.py index a70bd58..43ae50c 100644 --- a/tools/litert_cpu_options_identifier_patch.py +++ b/tools/litert_cpu_options_identifier_patch.py @@ -53,8 +53,9 @@ def require_cpu_options_identifier_patch_decision(compatibility_tag: str) -> boo return False raise RuntimeError( f"LiteRT CPU options identifier rollback has no explicit decision for " - f"{compatibility_tag}; check which identifier the pinned LiteRT and the " - "Linux/Windows prebuilt libLiteRt use, then record the decision in " + f"{compatibility_tag}; v0.19+ hosts compile LiteRT in (#111), so the " + "rollback is needed only if a host links a separate libLiteRt that reads " + "another identifier than the pinned LiteRT writes. Record the decision in " "tools/litert_cpu_options_identifier_patch.py and " "docs/upstream_patch_audit.md") diff --git a/tools/litert_lm_finish_reason_smoke.py b/tools/litert_lm_finish_reason_smoke.py index 5a4c2fb..b23e967 100644 --- a/tools/litert_lm_finish_reason_smoke.py +++ b/tools/litert_lm_finish_reason_smoke.py @@ -3,7 +3,8 @@ Each scenario streams one conversation turn through stream_proxy_* and checks the reason reported after the final callback. With --abi-only it only checks -that a built runtime reports the finish-reason ABI version its tag requires. +that a built runtime reports the finish-reason and in-process LiteRT ABI +versions its tag requires. """ from __future__ import annotations @@ -18,6 +19,7 @@ from pathlib import Path from litert_lm_c_api import CApi +from litert_lm_symbols import in_process_litert_abi_version from stream_finish_reason_patch import require_finish_reason_patch_decision FINISH_REASONS = { @@ -184,6 +186,22 @@ def expected_abi_version(upstream_tag: str) -> int: return 1 if require_finish_reason_patch_decision(upstream_tag) else 0 +def in_process_litert_matches(library, upstream_tag: str) -> bool: + expected = in_process_litert_abi_version(upstream_tag) + try: + marker = library.litert_lm_native_in_process_litert_abi_version + except AttributeError: + print("runtime lacks litert_lm_native_in_process_litert_abi_version", + file=sys.stderr) + return False + marker.restype = ctypes.c_int32 + marker.argtypes = [] + version = marker() + print(f"litert_lm_native_in_process_litert_abi_version() = {version}; " + f"{upstream_tag} expects {expected}") + return version == expected + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--library", type=Path, required=True) @@ -210,7 +228,8 @@ def main() -> int: expected = expected_abi_version(args.upstream_tag) print(f"stream_proxy_finish_reason_abi_version() = {version}; " f"{args.upstream_tag} expects {expected}") - return 0 if version == expected else 1 + in_process = in_process_litert_matches(library, args.upstream_tag) + return 0 if version == expected and in_process else 1 if version != 1: print(f"stream_proxy_finish_reason_abi_version() = {version}; " "this runtime does not report finish reasons", file=sys.stderr) diff --git a/tools/litert_lm_symbols.py b/tools/litert_lm_symbols.py index 810b091..f475c68 100644 --- a/tools/litert_lm_symbols.py +++ b/tools/litert_lm_symbols.py @@ -48,6 +48,7 @@ b"stream_proxy_get_finish_reason", b"stream_proxy_allocation_failure_abi_version", b"stream_proxy_has_allocation_failure", + b"litert_lm_native_in_process_litert_abi_version", ] V0_16_ASR_BRIDGE_SYMBOLS = [ @@ -130,6 +131,16 @@ def uses_c_api_v1(upstream_tag: str) -> bool: return is_at_least(upstream_tag, (0, 19, 0)) +def in_process_litert_abi_version(upstream_tag: str) -> int: + """Value of litert_lm_native_in_process_litert_abi_version() for this baseline. + + From v0.19 every platform compiles LiteRT into the runtime (#111), and the + build verifies it. Earlier desktop runtimes load upstream's prebuilt + libLiteRt, and earlier mobile linkage was never verified, so they report 0. + """ + return 1 if is_at_least(upstream_tag, (0, 19, 0)) else 0 + + def has_prebuilt_topk_samplers(upstream_tag: str) -> bool: """v0.19 links TopK samplers from source and ships no prebuilt sampler.""" return not is_at_least(upstream_tag, (0, 19, 0)) diff --git a/tools/test_build_upstream_runtime.py b/tools/test_build_upstream_runtime.py index 6db6762..f230f73 100644 --- a/tools/test_build_upstream_runtime.py +++ b/tools/test_build_upstream_runtime.py @@ -3,6 +3,7 @@ import subprocess import tempfile import unittest +from contextlib import ExitStack from types import SimpleNamespace from pathlib import Path from unittest.mock import patch @@ -156,27 +157,52 @@ def test_desktop_build_selects_shared_runtime_only_for_v016_to_v018(self) -> Non dynamic = platform in {"linux", "windows"} and tag not in {"v0.15.0", "v0.19.0", "v0.19.1"} self.assertEqual("--define=litert_runtime_link_mode=dynamic" in command, dynamic) self.assertEqual("--define=litert_link_capi_so=true" in command, not dynamic) + self.assertEqual("--define=litert_lm_in_process_litert=true" in command, + tag in {"v0.19.0", "v0.19.1"}) self.assertIn("--define=resolve_symbols_in_exec=false", command) - def test_v019_desktop_host_must_not_import_shared_litert(self) -> None: + def test_v019_runtime_must_not_import_shared_litert(self) -> None: cases = (("linux", ["libLiteRt.so", "libc.so.6"], ["libwebgpu_dawn.so", "libc.so.6"]), - ("windows", {"libLiteRt.dll": frozenset()}, {"KERNEL32.dll": frozenset()})) + ("android", ["libLiteRt.so", "libc.so"], ["libwebgpu_dawn.so", "libc.so"]), + ("windows", {"libLiteRt.dll": frozenset()}, {"KERNEL32.dll": frozenset()}), + ("macos", ["@rpath/libLiteRtLm.dylib", "@rpath/libLiteRt.dylib"], + ["@rpath/libLiteRtLm.dylib", "@rpath/libwebgpu_dawn.dylib"]), + ("ios", ["@rpath/LiteRtLm.framework/LiteRtLm", "@rpath/LiteRt.framework/LiteRt"], + ["@rpath/LiteRtLm.framework/LiteRtLm", "/usr/lib/libc++.1.dylib"])) for platform, shared, static in cases: - with self.subTest(platform=platform), tempfile.TemporaryDirectory() as temp: - output = Path(temp) / "host" - with patch.object(build_upstream_runtime, "elf_needed_libraries", return_value=shared), \ - patch.object(build_upstream_runtime.windows_runtime, "read_pe", - return_value=SimpleNamespace(all_imports=lambda: shared)): + with self.subTest(platform=platform): + output = Path("host") + with self._imports(shared): with self.assertRaisesRegex(RuntimeError, "#111"): - build_upstream_runtime.validate_desktop_litert_linkage(output, platform, "v0.19.1") - build_upstream_runtime.validate_desktop_litert_linkage(output, platform, "v0.18.1") - with patch.object(build_upstream_runtime, "elf_needed_libraries", return_value=static), \ - patch.object(build_upstream_runtime.windows_runtime, "read_pe", - return_value=SimpleNamespace(all_imports=lambda: static)): - build_upstream_runtime.validate_desktop_litert_linkage(output, platform, "v0.19.1") - with patch.object(build_upstream_runtime, "elf_needed_libraries") as needed: - build_upstream_runtime.validate_desktop_litert_linkage(Path("libLiteRtLm.so"), "android", "v0.19.1") - needed.assert_not_called() + build_upstream_runtime.validate_in_process_litert_linkage(output, platform, "v0.19.1") + build_upstream_runtime.validate_in_process_litert_linkage(output, platform, "v0.18.1") + with self._imports(static): + build_upstream_runtime.validate_in_process_litert_linkage(output, platform, "v0.19.1") + + def test_main_rejects_shared_litert_on_both_build_paths(self) -> None: + for source_root in (True, False): + with self.subTest(source_root=source_root), tempfile.TemporaryDirectory() as temp: + argv = ["build_upstream_runtime.py", "--upstream-tag", "v0.19.1", + "--platform", "linux", "--arch", "x64"] + if source_root: + argv += ["--source-root", temp] + with patch("sys.argv", argv), \ + patch.object(build_upstream_runtime, "download_upstream", return_value=Path(temp)), \ + patch.object(build_upstream_runtime, "build_runtime", return_value=Path(temp) / "libLiteRtLm.so"), \ + patch.object(build_upstream_runtime, "validate_exported_symbols"), \ + patch.object(build_upstream_runtime, "stage_runtime") as stage, \ + self._imports(["libLiteRt.so"]): + with self.assertRaisesRegex(RuntimeError, "#111"): + build_upstream_runtime.main() + stage.assert_not_called() + + def _imports(self, imported): + stack = ExitStack() + stack.enter_context(patch.object(build_upstream_runtime, "elf_needed_libraries", return_value=imported)) + stack.enter_context(patch.object(build_upstream_runtime, "macho_needed_libraries", return_value=imported)) + stack.enter_context(patch.object(build_upstream_runtime.windows_runtime, "read_pe", + return_value=SimpleNamespace(all_imports=lambda: imported))) + return stack def test_linux_stages_matching_prebuilt_core_even_over_stale_copy(self) -> None: for arch, target in (("x64", "linux_x86_64"), ("arm64", "linux_arm64")): diff --git a/tools/test_litert_lm_bridge.py b/tools/test_litert_lm_bridge.py index bd394cd..415bb9f 100644 --- a/tools/test_litert_lm_bridge.py +++ b/tools/test_litert_lm_bridge.py @@ -53,6 +53,7 @@ def test_windows_callback_exports_match_header_linkage(self) -> None: "stream_proxy_allocation_failure_abi_version", "stream_proxy_has_allocation_failure", "stream_proxy_delete", "stream_proxy_free_string", + "litert_lm_native_in_process_litert_abi_version", )}) def test_asr_session_adapter_preserves_both_upstream_apis(self) -> None: @@ -115,6 +116,10 @@ def test_stream_chunk_callback_adapter(self) -> None: def test_stream_chunk_finish_reason(self) -> None: self._compile_and_run(stream_chunk_api=True, finish_reason_api=True) + def test_in_process_litert_marker(self) -> None: + self._compile_and_run(stream_chunk_api=True, finish_reason_api=True, + in_process_litert=True) + def test_finish_reason_requires_stream_chunk_api(self) -> None: compiler = shutil.which("cc") or shutil.which("clang") if compiler is None: @@ -127,7 +132,8 @@ def test_finish_reason_requires_stream_chunk_api(self) -> None: self.assertIn("requires LITERT_LM_STREAM_CHUNK_API", result.stderr) def _compile_and_run( - self, *, stream_chunk_api: bool, finish_reason_api: bool = False + self, *, stream_chunk_api: bool, finish_reason_api: bool = False, + in_process_litert: bool = False, ) -> None: compiler = shutil.which("cc") or shutil.which("clang") if compiler is None: @@ -146,6 +152,8 @@ def _compile_and_run( command.append("-DLITERT_LM_STREAM_CHUNK_API=1") if finish_reason_api: command.append("-DLITERT_LM_FINISH_REASON_API=1") + if in_process_litert: + command.append("-DLITERT_LM_IN_PROCESS_LITERT=1") command.extend( [ str(BRIDGE_SOURCE), diff --git a/tools/test_litert_lm_finish_reason_smoke.py b/tools/test_litert_lm_finish_reason_smoke.py index 4a08ee4..803bdda 100644 --- a/tools/test_litert_lm_finish_reason_smoke.py +++ b/tools/test_litert_lm_finish_reason_smoke.py @@ -1,7 +1,9 @@ from __future__ import annotations +import io import json import unittest +from contextlib import redirect_stderr, redirect_stdout from types import SimpleNamespace from unittest.mock import Mock @@ -69,6 +71,16 @@ def test_abi_only_expects_version_one_only_for_patched_tags(self) -> None: with self.assertRaisesRegex(RuntimeError, "no explicit decision"): smoke.expected_abi_version("v0.19.2") + def test_abi_only_requires_the_tag_in_process_litert_marker(self) -> None: + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + self.assertFalse(smoke.in_process_litert_matches(SimpleNamespace(), "v0.19.1")) + for version, tag, expected in ((1, "v0.19.1", True), (0, "v0.19.1", False), + (0, "v0.18.1", True), (1, "v0.18.1", False)): + library = SimpleNamespace( + litert_lm_native_in_process_litert_abi_version=Mock(return_value=version)) + with self.subTest(version=version, tag=tag): + self.assertEqual(smoke.in_process_litert_matches(library, tag), expected) + if __name__ == "__main__": unittest.main() diff --git a/tools/test_litert_lm_symbols.py b/tools/test_litert_lm_symbols.py index 44cd0d5..e836244 100644 --- a/tools/test_litert_lm_symbols.py +++ b/tools/test_litert_lm_symbols.py @@ -9,6 +9,7 @@ V0_15_C_API_SYMBOLS, V0_16_ASR_BRIDGE_SYMBOLS, has_asr_bridge, + in_process_litert_abi_version, required_bridge_symbols, required_c_api_symbols, uses_stream_chunk_api, @@ -51,6 +52,12 @@ def test_every_stream_bridge_export_is_required_for_every_tag(self) -> None: for tag in ("v0.15.0", "v0.17.1", "v0.18.0"): self.assertTrue(set(BRIDGE_SYMBOLS).issubset(required_bridge_symbols(tag))) + def test_in_process_litert_marker_starts_at_v019(self) -> None: + for tag in ("v0.15.0", "v0.18.1"): + self.assertEqual(in_process_litert_abi_version(tag), 0) + for tag in ("v0.19.0", "v0.19.1", "v0.20.0"): + self.assertEqual(in_process_litert_abi_version(tag), 1) + if __name__ == "__main__": unittest.main()