This is an automated email from the ASF dual-hosted git repository.

chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new cd3948ce Refactor Bazel build with custom proto rules and build all 
test targets (#3313)
cd3948ce is described below

commit cd3948ceac996e1cee49263e12b0f6746207f24c
Author: Bright Chen <[email protected]>
AuthorDate: Sun May 31 10:48:30 2026 +0800

    Refactor Bazel build with custom proto rules and build all test targets 
(#3313)
---
 .bazelrc                                           |  51 ++++-
 .github/workflows/ci-linux.yml                     |  61 +++---
 BUILD.bazel                                        | 132 ++++++------
 CMakeLists.txt                                     |   4 +-
 MODULE.bazel                                       |  12 +-
 .../tools/BUILD.bazel                              |  19 +-
 bazel/tools/brpc_proto_library.bzl                 | 138 ++++++++++++
 bazel/tools/proto_gen.bzl                          | 240 +++++++++++++++++++++
 cmake/CMakeLists.download_gtest.in                 |   2 +-
 docs/cn/bthread_tracer.md                          |   6 +-
 example/BUILD.bazel                                |  34 +--
 .../libunwind/1.8.3.brpc-no-unwind/source.json     |   2 +-
 src/bthread/task_tracer.cpp                        |  20 +-
 src/butil/endpoint.cpp                             |  29 ++-
 src/butil/third_party/symbolize/symbolize.cc       | 115 +++++++---
 src/bvar/detail/percentile.h                       |  18 +-
 test/BUILD.bazel                                   | 171 ++++++---------
 test/brpc_alpn_protocol_unittest.cpp               |   6 +-
 test/brpc_http_rpc_protocol_unittest.cpp           |   9 +-
 test/brpc_protobuf_json_unittest.cpp               |   7 +-
 test/brpc_streaming_rpc_unittest.cpp               |   4 +-
 test/bthread_mutex_unittest.cpp                    |   3 +-
 .../generate_unittests.bzl                         |  31 +--
 test/root_runfiles.bzl                             |  79 +++++++
 24 files changed, 881 insertions(+), 312 deletions(-)

diff --git a/.bazelrc b/.bazelrc
index 2ee10dda..abf05fc6 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -13,12 +13,29 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-#
+# Bazel doesn't need more than 200MB of memory for local build based on memory 
profiling:
+# 
https://docs.bazel.build/versions/master/skylark/performance.html#memory-profiling
+# The default JVM max heapsize is 1/4 of physical memory up to 32GB which 
could be large
+# enough to consume all memory constrained by cgroup in large host.
+# Limiting JVM heapsize here to let it do GC more when approaching the limit to
+# leave room for compiler/linker.
+# The number 3G is chosen heuristically to both support large VM and small VM 
with RBE.
+# Startup options cannot be selected via config.
+startup --host_jvm_args=-Xmx3g
+startup --host_jvm_args="-DBAZEL_TRACK_SOURCE_DIRECTORIES=1"
+
 # Default build options. These are applied first and unconditionally.
-#
 common --registry=https://bcr.bazel.build
 common --registry=https://baidu.github.io/babylon/registry
+common --registry=https://raw.githubusercontent.com/apache/brpc/master/registry
 
+build --verbose_failures
+# Keep SHT_SYMTAB in built binaries so google::Symbolize can resolve
+# in-binary functions (e.g. TestBody() in test binaries) by name
+# instead of falling back to "<unknown>". Bazel's default
+# `--strip=sometimes` strips debug/symbol sections in fastbuild mode,
+# which is what `bazel test` uses unless `-c dbg` is given.
+build --strip=never
 build --cxxopt="-std=c++17"
 build --copt="-fno-omit-frame-pointer"
 # Use gnu17 for asm keyword.
@@ -33,8 +50,16 @@ build --features=per_object_debug_info
 # We already have absl in the build, define absl=1 to tell googletest to use 
absl for backtrace.
 build --define absl=1
 
-# For brpc.
-test --define=BRPC_BUILD_FOR_UNITTEST=true
+# For UT.
+build:test --define BRPC_BUILD_FOR_UNITTEST=true
+# Hide libunwind's `_Unwind_*` symbols so they don't preempt libgcc_s at
+# runtime. Without this, pthread_exit / C++ exception unwinding crashes
+# when libunwind.so appears earlier in the dynamic link chain.
+# See registry/modules/1.8.1/overlay/BUILD.bazel for details.
+build:test --define with_bthread_tracer=false
+build:test --define libunwind_hide_unwind_symbols=true
+
+test --config=test
 test --test_output=streamed
 
 # Pass PATH, CC, CXX and LLVM_CONFIG variables from the environment.
@@ -42,3 +67,21 @@ build --action_env=CC
 build --action_env=CXX
 build --action_env=LLVM_CONFIG
 build --action_env=PATH
+
+# Basic ASAN
+build:asan --define=with_asan=true
+build:asan --copt -fsanitize=address
+build:asan --linkopt -fsanitize=address
+# ASAN needs -O1 to get reasonable performance.
+build:asan --copt -O1
+build:asan --copt -fno-optimize-sibling-calls
+
+# macOS ASAN
+build:macos-asan --config=asan
+# Workaround, see https://github.com/bazelbuild/bazel/issues/6932
+build:macos-asan --copt -Wno-macro-redefined
+build:macos-asan --copt -D_FORTIFY_SOURCE=0
+# Dynamic link cause issues like: `dyld: malformed mach-o: load commands size 
(59272) > 32768`
+build:macos-asan --dynamic_mode=off
+
+test:asan 
--test_env=ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1
diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml
index f2d6d692..8a36af60 100644
--- a/.github/workflows/ci-linux.yml
+++ b/.github/workflows/ci-linux.yml
@@ -103,33 +103,28 @@ jobs:
         protobuf-install-dir: /protobuf-3.21.12
         config-brpc-options: --cc=gcc --cxx=g++ --werror
 
-  gcc-compile-with-bazel:
+  gcc-unittest-with-bazel:
     runs-on: ubuntu-22.04
     steps:
     - uses: actions/checkout@v2
-    - run: bazel build --verbose_failures -- //... -//example/...
-
-  gcc-compile-with-boringssl:
-    runs-on: ubuntu-22.04
-    steps:
-    - uses: actions/checkout@v2
-    - run: bazel build --verbose_failures --define with_mesalink=false 
--define with_glog=true --define with_thrift=true --define 
BRPC_WITH_BORINGSSL=true -- //... -//example/...
+    - run: bazel test //test/...
 
   gcc-compile-with-bazel-all-options:
     runs-on: ubuntu-22.04
     steps:
     - uses: actions/checkout@v2
     - run: |
-        bazel build --verbose_failures \
-                    --define with_mesalink=false \
+        bazel build --define with_mesalink=false \
                     --define with_glog=true \
                     --define with_thrift=true \
+                    --define BRPC_WITH_BORINGSSL=true \
                     --define with_debug_bthread_sche_safety=true \
                     --define with_debug_lock=true \
                     --define with_asan=true \
                     --define with_bthread_tracer=true \
                     --define BRPC_WITH_NO_PTHREAD_MUTEX_HOOK=true \
-                    -- //... -//example/...
+                    --define with_babylon_counter=true \
+                    -- //:brpc
 
   clang-compile-with-make-protobuf:
     runs-on: ubuntu-22.04
@@ -161,34 +156,32 @@ jobs:
         protobuf-install-dir: /protobuf-3.21.12
         config-brpc-options: --cc=clang --cxx=clang++ --werror
 
-  clang-compile-with-bazel:
+  clang-unittest-with-bazel:
     runs-on: ubuntu-22.04
     steps:
     - uses: actions/checkout@v2
-    - run: bazel build --verbose_failures --action_env=CC=clang -- //... 
-//example/...
-
-  clang-compile-with-boringssl:
-    runs-on: ubuntu-22.04
-    steps:
-    - uses: actions/checkout@v2
-    - run: bazel build --verbose_failures --action_env=CC=clang --define 
with_mesalink=false --define with_glog=true --define with_thrift=true --define 
BRPC_WITH_BORINGSSL=true -- //... -//example/...
+    - run: |
+        bazel test --test_output=streamed \
+                   --action_env=CC=clang \
+                   //test/...
 
   clang-compile-with-bazel-all-options:
     runs-on: ubuntu-22.04
     steps:
     - uses: actions/checkout@v2
     - run: |
-        bazel build --verbose_failures \
-                    --action_env=CC=clang \
+        bazel build --action_env=CC=clang \
                     --define with_mesalink=false \
                     --define with_glog=true \
                     --define with_thrift=true \
+                    --define BRPC_WITH_BORINGSSL=true \
                     --define with_debug_bthread_sche_safety=true \
                     --define with_debug_lock=true \
                     --define with_asan=true \
                     --define with_bthread_tracer=true \
                     --define BRPC_WITH_NO_PTHREAD_MUTEX_HOOK=true \
-                    -- //... -//example/...
+                    --define with_babylon_counter=true \
+                    -- //:brpc
 
   clang-unittest:
     runs-on: ubuntu-22.04
@@ -226,11 +219,25 @@ jobs:
         cd test
         sh ./run_tests.sh
 
-  bazel-bvar-unittest:
+  clang-unittest-bazel-with-babylon-and-new-pb:
     runs-on: ubuntu-22.04
+    env:
+      TEST_PROTOBUF_VERSION: "34.1"
+      # protobuf >= 34.x uses new ProtoInfo fields (option_deps,
+      # extension_declarations) introduced in Bazel 8.x. The repo's
+      # .bazelversion (7.2.1) is too old. bazelisk honors USE_BAZEL_VERSION.
+      USE_BAZEL_VERSION: "8.3.1"
     steps:
     - uses: actions/checkout@v2
-    - run: bazel test --verbose_failures //test:bvar_test
-    - run: bazel test --verbose_failures --define with_babylon_counter=true 
//test:bvar_test
-    - run: bazel test --verbose_failures --action_env=CC=clang //test:bvar_test
-    - run: bazel test --verbose_failures --action_env=CC=clang --define 
with_babylon_counter=true //test:bvar_test
+    - name: Override protobuf version for testing
+      run: |
+        sed -i -E "s/(bazel_dep\(name = ['\"]protobuf['\"], version = 
['\"])[^'\"]+/\1${TEST_PROTOBUF_VERSION}/" MODULE.bazel
+        echo "After override:"
+        grep -E "bazel_dep\(name = ['\"]protobuf['\"]" MODULE.bazel
+        grep -qE "bazel_dep\(name = ['\"]protobuf['\"], version = 
['\"]${TEST_PROTOBUF_VERSION}['\"]" MODULE.bazel \
+          || { echo "ERROR: failed to override protobuf version in 
MODULE.bazel to ${TEST_PROTOBUF_VERSION}"; exit 1; }
+    - run: | 
+        bazel test --action_env=CC=clang  \
+                   --define with_babylon_counter=true \
+                   --define with_babylon_counter=true \
+                   //test:brpc_unittests
diff --git a/BUILD.bazel b/BUILD.bazel
index c763cb3a..22cb5085 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -13,8 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_proto_library", 
"objc_library")
+load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "objc_library")
+load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library")
 
 licenses(["notice"])  # Apache v2
 
@@ -22,43 +22,46 @@ exports_files(["LICENSE"])
 
 COPTS = [
     "-fno-omit-frame-pointer",
-    "-DBTHREAD_USE_FAST_PTHREAD_MUTEX",
-    "-D__const__=__unused__",
-    "-D_GNU_SOURCE",
-    "-DUSE_SYMBOLIZE",
-    "-DNO_TCMALLOC",
-    "-D__STDC_FORMAT_MACROS",
-    "-D__STDC_LIMIT_MACROS",
-    "-D__STDC_CONSTANT_MACROS",
 ] + select({
-    "//bazel/config:brpc_with_glog": ["-DBRPC_WITH_GLOG=1"],
-    "//conditions:default": ["-DBRPC_WITH_GLOG=0"],
-}) + select({
-    "//bazel/config:brpc_with_mesalink": ["-DUSE_MESALINK"],
-    "//conditions:default": [""],
-}) + select({
-    "//bazel/config:brpc_with_thrift": ["-DENABLE_THRIFT_FRAMED_PROTOCOL=1"],
-    "//conditions:default": [""],
-}) + select({
-    "//bazel/config:brpc_with_thrift_legacy_version": [],
-    "//conditions:default": ["-DTHRIFT_STDCXX=std"],
-}) + select({
-    "//bazel/config:brpc_with_rdma": ["-DBRPC_WITH_RDMA=1"],
-    "//conditions:default": [""],
-}) + select({
-    "//bazel/config:brpc_with_debug_bthread_sche_safety": 
["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=1"],
-    "//conditions:default": ["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=0"],
-}) + select({
-    "//bazel/config:brpc_with_debug_lock": ["-DBRPC_DEBUG_LOCK=1"],
-    "//conditions:default": ["-DBRPC_DEBUG_LOCK=0"],
-}) + select({
     "//bazel/config:brpc_with_asan": ["-fsanitize=address"],
-    "//conditions:default": [""],
-}) + select({
-    "//bazel/config:brpc_with_no_pthread_mutex_hook": 
["-DNO_PTHREAD_MUTEX_HOOK"],
-    "//conditions:default": [""],
+    "//conditions:default": [],
 })
 
+DEFINES = [
+    "BTHREAD_USE_FAST_PTHREAD_MUTEX",
+    "__const__=__unused__",
+    "_GNU_SOURCE",
+    "USE_SYMBOLIZE",
+    "NO_TCMALLOC",
+    "__STDC_FORMAT_MACROS",
+    "__STDC_LIMIT_MACROS",
+    "__STDC_CONSTANT_MACROS",
+] + select({
+     "//bazel/config:brpc_with_glog": ["BRPC_WITH_GLOG=1"],
+     "//conditions:default": ["BRPC_WITH_GLOG=0"],
+ }) + select({
+     "//bazel/config:brpc_with_mesalink": ["USE_MESALINK"],
+     "//conditions:default": [],
+ }) + select({
+     "//bazel/config:brpc_with_thrift": ["ENABLE_THRIFT_FRAMED_PROTOCOL=1"],
+     "//conditions:default": [],
+ }) + select({
+     "//bazel/config:brpc_with_thrift_legacy_version": [],
+     "//conditions:default": ["THRIFT_STDCXX=std"],
+ }) + select({
+     "//bazel/config:brpc_with_rdma": ["BRPC_WITH_RDMA=1"],
+     "//conditions:default": [],
+ }) + select({
+     "//bazel/config:brpc_with_debug_bthread_sche_safety": 
["BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1"],
+     "//conditions:default": ["BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0"],
+ }) + select({
+     "//bazel/config:brpc_with_debug_lock": ["BRPC_DEBUG_LOCK=1"],
+     "//conditions:default": ["BRPC_DEBUG_LOCK=0"],
+ }) + select({
+     "//bazel/config:brpc_with_no_pthread_mutex_hook": 
["NO_PTHREAD_MUTEX_HOOK"],
+     "//conditions:default": [],
+ })
+
 LINKOPTS = [
     "-pthread",
     "-ldl",
@@ -93,7 +96,7 @@ LINKOPTS = [
     "//conditions:default": [],
 }) + select({
         "//bazel/config:brpc_with_asan": ["-fsanitize=address"],
-        "//conditions:default": [""],
+        "//conditions:default": [],
   })
 
 genrule(
@@ -331,13 +334,13 @@ cc_library(
         "src/butil/third_party/dmg_fp/dtoa.cc",
         ":config_h",
     ],
-    copts = COPTS + select({
+    defines = DEFINES + select({
         "//bazel/config:brpc_build_for_unittest": [
-            "-DBVAR_NOT_LINK_DEFAULT_VARIABLES",
-            "-DUNIT_TEST",
+            "UNIT_TEST",
         ],
         "//conditions:default": [],
     }),
+    copts = COPTS,
     includes = [
         "src/",
     ],
@@ -354,8 +357,14 @@ cc_library(
         "@bazel_tools//src/conditions:darwin": [":macos_lib"],
         "//conditions:default": [],
     }) + select({
-        "//bazel/config:brpc_with_boringssl": ["@boringssl//:ssl", 
"@boringssl//:crypto"],
-        "//conditions:default": ["@openssl//:ssl", "@openssl//:crypto"],
+        "//bazel/config:brpc_with_boringssl": [
+            "@boringssl//:ssl",
+            "@boringssl//:crypto"
+        ],
+        "//conditions:default": [
+            "@openssl//:ssl",
+            "@openssl//:crypto"
+        ],
     }),
 )
 
@@ -381,14 +390,13 @@ cc_library(
     defines = [] + select({
         "//bazel/config:with_babylon_counter": ["WITH_BABYLON_COUNTER=1"],
         "//conditions:default": [],
-    }),
-    copts = COPTS + select({
+    }) + select({
         "//bazel/config:brpc_build_for_unittest": [
-            "-DBVAR_NOT_LINK_DEFAULT_VARIABLES",
-            "-DUNIT_TEST",
+            "BVAR_NOT_LINK_DEFAULT_VARIABLES",
         ],
         "//conditions:default": [],
     }),
+    copts = COPTS,
     includes = [
         "src/",
     ],
@@ -475,7 +483,6 @@ cc_library(
     deps = [
         ":brpc_idl_options_cc_proto",
         ":butil",
-        "@com_google_protobuf//:protoc_lib",
     ],
 )
 
@@ -487,20 +494,11 @@ filegroup(
     visibility = ["//visibility:public"],
 )
 
-proto_library(
-    name = "brpc_idl_options_proto",
-    srcs = [":brpc_idl_options_proto_srcs"],
-    strip_import_prefix = "src",
-    visibility = ["//visibility:public"],
-    deps = [
-        "@com_google_protobuf//:descriptor_proto",
-    ],
-)
-
-cc_proto_library(
+brpc_proto_library(
     name = "brpc_idl_options_cc_proto",
+    srcs = [":brpc_idl_options_proto_srcs"],
+    include = "src",
     visibility = ["//visibility:public"],
-    deps = [":brpc_idl_options_proto"],
 )
 
 filegroup(
@@ -512,21 +510,12 @@ filegroup(
     visibility = ["//visibility:public"],
 )
 
-proto_library(
-    name = "brpc_internal_proto",
-    srcs = [":brpc_internal_proto_srcs"],
-    strip_import_prefix = "src",
-    visibility = ["//visibility:public"],
-    deps = [
-        ":brpc_idl_options_proto",
-        "@com_google_protobuf//:descriptor_proto",
-    ],
-)
-
-cc_proto_library(
+brpc_proto_library(
     name = "brpc_internal_cc_proto",
+    srcs = [":brpc_internal_proto_srcs"],
+    include = "src",
+    deps = [":brpc_idl_options_cc_proto"],
     visibility = ["//visibility:public"],
-    deps = [":brpc_internal_proto"],
 )
 
 cc_library(
@@ -587,5 +576,6 @@ cc_binary(
     deps = [
         ":brpc",
         ":brpc_idl_options_cc_proto",
+        "@com_google_protobuf//:protoc_lib",
     ],
 )
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 77703a46..98418355 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -206,11 +206,13 @@ if(Protobuf_VERSION GREATER 4.21)
         absl::hash
         absl::layout
         absl::log_initialize
+        absl::log_globals
         absl::log_severity
         absl::memory
         absl::node_hash_map
         absl::node_hash_set
-        absl::optional
+        absl::random_distributions
+        absl::random_random
         absl::span
         absl::status
         absl::statusor
diff --git a/MODULE.bazel b/MODULE.bazel
index 95f4e6b7..add40787 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -16,8 +16,17 @@ bazel_dep(name = "apple_support", version = "1.17.1")
 bazel_dep(name = 'rules_cc', version = '0.0.1')
 bazel_dep(name = 'rules_proto', version = '4.0.0')
 bazel_dep(name = 'zlib', version = '1.3.1.bcr.5', repo_name = 
'com_github_madler_zlib')
-bazel_dep(name = 'libunwind', version = '1.8.1', repo_name = 
'com_github_libunwind_libunwind')
 bazel_dep(name = 'babylon', version = '1.4.4')
+# --registry=https://raw.githubusercontent.com/apache/brpc/master/registry
+# `registry/modules/libunwind/` (see the 
`--registry=https://raw.githubusercontent.com/apache/brpc/master/registry`
+# entry at the top of `.bazelrc`). The version suffix `.brpc-no-unwind` marks
+# this as brpc's self-maintained variant whose `src/unwind/*.c` (the GCC
+# `_Unwind_*` ABI compatibility layer) is gated by the
+# `--define=libunwind_hide_unwind_symbols=true` switch. Hiding those
+# `_Unwind_*` symbols is required for `--define=with_bthread_tracer=true`
+# to work without crashing in pthread_exit / exception unwinding.
+# See docs/cn/bthread_tracer.md for background.
+bazel_dep(name = 'libunwind', version = '1.8.1.brpc-no-unwind', repo_name = 
'com_github_libunwind_libunwind')
 
 # --registry=https://baidu.github.io/babylon/registry
 bazel_dep(name = 'leveldb', version = '1.23', repo_name = 
'com_github_google_leveldb')
@@ -34,6 +43,7 @@ single_version_override(
 bazel_dep(name = 'thrift', version = '0.21.0', repo_name = 'org_apache_thrift')
 
 # test only
+bazel_dep(name = "gperftools", version = "2.18.1", dev_dependency = True)
 bazel_dep(name = 'googletest', version = '1.14.0.bcr.1', repo_name = 
'com_google_googletest', dev_dependency = True)
 bazel_dep(name = 'hedron_compile_commands', dev_dependency = True)
 git_override(
diff --git a/cmake/CMakeLists.download_gtest.in b/bazel/tools/BUILD.bazel
similarity index 64%
copy from cmake/CMakeLists.download_gtest.in
copy to bazel/tools/BUILD.bazel
index df020c89..3b448305 100644
--- a/cmake/CMakeLists.download_gtest.in
+++ b/bazel/tools/BUILD.bazel
@@ -13,18 +13,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cmake_minimum_required(VERSION 2.8.10)
+# This BUILD file marks bazel/tools/ as a Bazel package so that loads
+# of the form `//bazel/tools:xxx.bzl` are valid. The directory only
+# hosts .bzl files (brpc_proto_library.bzl / proto_gen.bzl) and does
+# not define any build targets.
 
-project(googletest-download NONE)
+package(default_visibility = ["//visibility:public"])
 
-include(ExternalProject)
-ExternalProject_Add(googletest
-  GIT_REPOSITORY    https://github.com/google/googletest.git
-  GIT_TAG           release-1.12.0
-  SOURCE_DIR        "${PROJECT_BINARY_DIR}/googletest-src"
-  BINARY_DIR        "${PROJECT_BINARY_DIR}/googletest-build"
-  CONFIGURE_COMMAND ""
-  BUILD_COMMAND     ""
-  INSTALL_COMMAND   ""
-  TEST_COMMAND      ""
-)
+exports_files(glob(["*.bzl"]))
diff --git a/bazel/tools/brpc_proto_library.bzl 
b/bazel/tools/brpc_proto_library.bzl
new file mode 100644
index 00000000..22a3c00b
--- /dev/null
+++ b/bazel/tools/brpc_proto_library.bzl
@@ -0,0 +1,138 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# brpc_proto_library: a proto -> cc_library macro that is robust against
+# protobuf and Bazel version churn.
+#
+# Background:
+#   - Old protobuf (< 3.21) used to expose a `cc_proto_library` macro
+#     via `@com_google_protobuf//:protobuf.bzl`; newer protobuf removed
+#     that macro.
+#   - Newer protobuf expects callers to use Bazel's native
+#     `proto_library` / `cc_proto_library` (or the `cc_proto_library`
+#     re-exported from `@com_google_protobuf//bazel:cc_proto_library.bzl`),
+#     but older protobuf does not surface a complete `ProtoInfo`, so
+#     the native rules cannot build it.
+#   - Bazel 8 additionally removed `cc_proto_library` from
+#     `@rules_cc//cc:defs.bzl` (the load now resolves to a stub rule
+#     that fails analysis), forcing every caller to either load it
+#     from `@com_google_protobuf` or replace it.
+#   - Bazel's `load()` is static and cannot be conditional on the
+#     protobuf version, so a single brpc_proto_library.bzl cannot
+#     directly satisfy both old and new protobuf by switching imports.
+#
+# Solution (inspired by 
https://github.com/trpc-group/trpc-cpp/blob/a69d2950f4a9c16b3cb40a2750c69d8940c96820/trpc/trpc.bzl):
+# We implement the proto compilation rule from scratch (see
+# proto_gen.bzl in this directory). Dependency information is
+# propagated through Bazel's native `ProtoInfo` provider, and the
+# default well-known-proto source is
+# `@com_google_protobuf//:descriptor_proto` -- a `proto_library`
+# whose name has been stable across protobuf 3.x / 27.x / 34.x.
+load("//bazel/tools:proto_gen.bzl", "brpc_proto_gen")
+
+# `:descriptor_proto` is the protobuf repo's `proto_library` target;
+# its label has been stable since protobuf 3.x. We consume it through
+# Bazel's native `ProtoInfo` provider, which is uniform across PB
+# versions. If a caller's .proto needs additional well-known protos
+# (any.proto, timestamp.proto, ...), they can pass extra entries via
+# the `proto_deps` argument.
+_DEFAULT_PROTO_DEPS = ["@com_google_protobuf//:descriptor_proto"]
+
+def brpc_proto_library(
+        name,
+        srcs,
+        deps = [],
+        include = None,
+        proto_deps = None,
+        visibility = None,
+        testonly = 0):
+    """Generate a cc_library from a set of .proto files.
+
+    Args:
+      name: target name.
+      srcs: list of .proto files, paths relative to the current package.
+      deps: list of other `brpc_proto_library` targets this target
+            depends on. The macro internally translates these labels
+            to the underlying `<name>_genproto` rule so that .proto
+            include paths and sources propagate.
+      include: protoc `-I` root AND the resulting cc_library `includes`
+               root, relative to the current package.
+               When omitted, "" or None, the include root is the
+               current package itself (suitable for .proto files
+               sitting directly under the package root, as in `test/`
+               and `example/...`). The root `BUILD.bazel` of brpc must
+               pass `"src"` so that code can reference the protos as
+               `import "brpc/foo.proto"`.
+      proto_deps: list of native `proto_library` dependencies
+                  (well-known protos or external .proto libraries).
+                  Defaults to
+                  `["@com_google_protobuf//:descriptor_proto"]`.
+                  Pass `[]` explicitly to disable the default; pass
+                  None (the default) to use it.
+      visibility: same semantics as cc_library.
+      testonly: same semantics as cc_library.
+    """
+
+    real_include = include if include != None else ""
+    real_proto_deps = proto_deps if proto_deps != None else _DEFAULT_PROTO_DEPS
+
+    gen_name = name + "_genproto"
+    brpc_proto_gen(
+        name = gen_name,
+        srcs = srcs,
+        # Rewrite each `brpc_proto_library` dep to its underlying
+        # `<dep>_genproto` rule, which exports BrpcProtoInfo.
+        deps = [d + "_genproto" for d in deps],
+        include = real_include,
+        proto_deps = real_proto_deps,
+        visibility = visibility,
+        testonly = testonly,
+    )
+
+    # Split the generated files into .pb.h / .pb.cc via OutputGroupInfo
+    # and feed them to cc_library's hdrs / srcs separately. Bazel
+    # rejects declaring the same File in both hdrs and srcs, so a
+    # plain DefaultInfo wouldn't work here.
+    native.filegroup(
+        name = gen_name + "_hdrs",
+        srcs = [":" + gen_name],
+        output_group = "hdrs",
+        visibility = visibility,
+        testonly = testonly,
+    )
+    native.filegroup(
+        name = gen_name + "_srcs",
+        srcs = [":" + gen_name],
+        output_group = "srcs",
+        visibility = visibility,
+        testonly = testonly,
+    )
+
+    native.cc_library(
+        name = name,
+        srcs = [":" + gen_name + "_srcs"],
+        hdrs = [":" + gen_name + "_hdrs"],
+        # cc_library `includes` is required, otherwise the .pb.cc
+        # files inside this cc_library cannot find the .pb.h headers
+        # they just generated (the headers live under
+        # bazel-bin/<package>/<include>/...). When include="" we pass
+        # "." to mean "the current package itself"; Bazel then exposes
+        # both `-I <package>` and `-I bazel-bin/<package>`
+        # automatically to dependents.
+        includes = [real_include if real_include else "."],
+        deps = deps + ["@com_google_protobuf//:protobuf"],
+        visibility = visibility,
+        testonly = testonly,
+    )
diff --git a/bazel/tools/proto_gen.bzl b/bazel/tools/proto_gen.bzl
new file mode 100644
index 00000000..554d24df
--- /dev/null
+++ b/bazel/tools/proto_gen.bzl
@@ -0,0 +1,240 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# brpc's self-implemented proto -> .pb.{h,cc} compilation rule.
+#
+# Cross-protobuf-version compatibility strategy (inspired by trpc.bzl):
+#   1) Do NOT load `@com_google_protobuf//:protobuf.bzl` and do NOT
+#      call `native.cc_proto_library` -- this side-steps the double
+#      incompatibility of newer protobuf removing the macro and older
+#      protobuf missing a complete `ProtoInfo` provider.
+#   2) Do NOT hardcode any protobuf-repo-internal filegroup names
+#      (such as `well_known_protos` / `descriptor_proto_srcs`) -- the
+#      names of those filegroups have shifted between protobuf 3.x
+#      and 27.x+.
+#   3) Propagate dependencies via Bazel's native `ProtoInfo` provider:
+#      callers pass `proto_library` targets through `proto_deps`
+#      (defaulting to `@com_google_protobuf//:descriptor_proto`, a
+#      `proto_library` whose label has been stable across PB versions),
+#      and this rule walks `transitive_sources` / `transitive_proto_path`
+#      to collect .proto source files and protoc `-I` paths -- fully
+#      decoupled from the PB version.
+#   4) Among `brpc_proto_gen` targets themselves, dependencies are
+#      propagated via the custom `BrpcProtoInfo` provider declared
+#      below.
+#
+# Note on minimum Bazel version: this rule itself does not call any
+# Bazel-version-specific API, but it still consumes `ProtoInfo` from
+# `proto_library` targets (e.g. `descriptor_proto`) defined inside
+# the protobuf repo. Protobuf >= 34 calls
+# `ProtoInfo(option_deps = ..., extension_declarations = ...)` in its
+# own `proto_library` implementation, and those fields are only
+# accepted by Bazel >= 8. In other words, the minimum Bazel version
+# brpc requires is whatever the bundled protobuf version requires --
+# we just don't add any extra requirement on top of that.
+
+BrpcProtoInfo = provider(
+    doc = "Carries transitive .proto sources and protoc -I flags between 
brpc_proto_gen targets.",
+    fields = {
+        "transitive_srcs": "depset of all transitive .proto Files.",
+        "transitive_imports": "depset of strings, all -I flags needed by 
protoc.",
+    },
+)
+
+def _resolve_include_dir(ctx):
+    """Compute this target's protoc include root (workspace-relative).
+
+    Examples:
+      ctx.label.package = ""     + include = "src" -> "src"
+      ctx.label.package = "test" + include = ""    -> "test"
+      ctx.label.package = ""     + include = ""    -> "."
+    """
+    pkg = ctx.label.package
+    inc = ctx.attr.include.rstrip("/")
+    if pkg and inc:
+        return pkg + "/" + inc
+    if pkg:
+        return pkg
+    if inc:
+        return inc
+    return "."
+
+def _proto_gen_impl(ctx):
+    srcs = ctx.files.srcs
+    include_dir = _resolve_include_dir(ctx)
+    bin_root = ctx.bin_dir.path
+
+    # `-I` flags for this target itself: the source-tree root plus
+    # the corresponding bin-dir root. The bin-dir entry is needed
+    # when a transitive dep generates .proto files into bazel-bin
+    # (e.g. via a custom code generator).
+    own_imports = ["-I" + include_dir]
+    if include_dir == ".":
+        own_imports.append("-I" + bin_root)
+    else:
+        own_imports.append("-I" + bin_root + "/" + include_dir)
+
+    # Collect transitive info from other `brpc_proto_gen` deps.
+    dep_srcs_list = [d[BrpcProtoInfo].transitive_srcs for d in ctx.attr.deps]
+    dep_imports_list = [d[BrpcProtoInfo].transitive_imports for d in 
ctx.attr.deps]
+
+    # Collect native `proto_library` deps (well-known protos or
+    # external .proto libraries). Key design: .proto sources and
+    # `-I` paths are obtained via Bazel's native `ProtoInfo`,
+    # avoiding any reliance on protobuf-repo-internal filegroup
+    # names (descriptor_proto_srcs / well_known_protos / ...).
+    # `transitive_proto_path` already accounts for the
+    # `_virtual_imports` paths created by `strip_import_prefix`, so
+    # protoc can consume the paths directly.
+    #
+    # Important: `strip_import_prefix` causes .proto files to live at
+    # virtual paths under bazel-out/<config>/bin/<workspace_root>/<virtual>
+    # (see trpc.bzl for prior art). For every `proto_dep` repo we
+    # therefore expose FOUR candidate `-I` paths to be safe:
+    #   1) <workspace_root>                  -- source root (plain 
proto_library)
+    #   2) <bin_dir>/<workspace_root>        -- virtual root after 
strip_import_prefix
+    #   3) <workspace_root>/src              -- fallback for older PB 
descriptor_proto without strip
+    #   4) <bin_dir>/<workspace_root>/src    -- same as #3, under bin-dir
+    # protoc only warns on non-existent `-I` paths, so this is harmless.
+    proto_dep_src_depsets = []
+    proto_dep_imports = []
+    extra_pb_root_imports = []
+    # Belt-and-suspenders: also feed each `proto_dep`'s default outputs
+    # (i.e. the .proto files themselves) into `inputs`, in case the
+    # ProtoInfo's transitive_sources does not cover everything we need.
+    proto_dep_src_depsets.append(depset(direct = ctx.files.proto_deps))
+    for pd in ctx.attr.proto_deps:
+        pi = pd[ProtoInfo]
+        proto_dep_src_depsets.append(pi.transitive_sources)
+        for path in pi.transitive_proto_path.to_list():
+            proto_dep_imports.append("-I" + path)
+        wsroot = pd.label.workspace_root
+        if wsroot:
+            extra_pb_root_imports.append("-I" + wsroot)
+            extra_pb_root_imports.append("-I" + bin_root + "/" + wsroot)
+            extra_pb_root_imports.append("-I" + wsroot + "/src")
+            extra_pb_root_imports.append("-I" + bin_root + "/" + wsroot + 
"/src")
+    # Deduplicate the workspace-level `-I` entries so the same repo
+    # is not listed multiple times when several proto_deps share it.
+    proto_dep_imports.extend(depset(extra_pb_root_imports).to_list())
+
+    all_imports = depset(
+        direct = own_imports + proto_dep_imports,
+        transitive = dep_imports_list,
+    )
+
+    # Output files: every .proto produces a .pb.h and a .pb.cc.
+    # NB: the `path` argument of `ctx.actions.declare_file(path)` is
+    # *relative to the current package*, while `src.short_path` is
+    # *relative to the workspace root*. The two differ by the package
+    # prefix; we must strip that prefix before calling declare_file,
+    # otherwise the outputs would land at
+    # bazel-bin/<pkg>/<pkg>/... (one level too deep).
+    pkg = ctx.label.package
+    pkg_prefix = pkg + "/" if pkg else ""
+    outs = []
+    for src in srcs:
+        if not src.short_path.endswith(".proto"):
+            fail("brpc_proto_gen srcs must be .proto, got: %s" % 
src.short_path)
+
+        rel = src.short_path
+        if pkg_prefix and rel.startswith(pkg_prefix):
+            rel = rel[len(pkg_prefix):]
+        base = rel[:-len(".proto")]
+        outs.append(ctx.actions.declare_file(base + ".pb.h"))
+        outs.append(ctx.actions.declare_file(base + ".pb.cc"))
+
+    # protoc's --cpp_out points at the include root under bin_root.
+    # After protoc organizes outputs by their import-relative path,
+    # the .pb.{h,cc} files land exactly where declare_file declared
+    # them above.
+    if include_dir == ".":
+        cpp_out_dir = bin_root
+    else:
+        cpp_out_dir = bin_root + "/" + include_dir
+
+    args = ctx.actions.args()
+    args.add_all(all_imports.to_list())
+    args.add("--cpp_out=" + cpp_out_dir)
+    args.add_all([s.path for s in srcs])
+
+    ctx.actions.run(
+        executable = ctx.executable.protoc,
+        arguments = [args],
+        inputs = depset(
+            direct = srcs,
+            transitive = dep_srcs_list + proto_dep_src_depsets,
+        ),
+        outputs = outs,
+        mnemonic = "BrpcProtoCompile",
+        progress_message = "BrpcProtoCompile %s" % ctx.label,
+    )
+
+    hdr_files = [f for f in outs if f.path.endswith(".pb.h")]
+    src_files = [f for f in outs if f.path.endswith(".pb.cc")]
+
+    return [
+        DefaultInfo(files = depset(outs)),
+        # Split outputs so the brpc_proto_library macro can route
+        # them to cc_library.hdrs vs cc_library.srcs separately.
+        OutputGroupInfo(
+            hdrs = depset(hdr_files),
+            srcs = depset(src_files),
+        ),
+        BrpcProtoInfo(
+            transitive_srcs = depset(direct = srcs, transitive = 
dep_srcs_list),
+            transitive_imports = all_imports,
+        ),
+    ]
+
+brpc_proto_gen = rule(
+    implementation = _proto_gen_impl,
+    attrs = {
+        "srcs": attr.label_list(
+            allow_files = [".proto"],
+            mandatory = True,
+        ),
+        # Other `brpc_proto_gen` targets; deps are propagated via the
+        # custom `BrpcProtoInfo` provider.
+        "deps": attr.label_list(
+            providers = [BrpcProtoInfo],
+            default = [],
+        ),
+        # The include root, relative to the current BUILD package
+        # (e.g. "src" for the brpc root BUILD).
+        "include": attr.string(default = ""),
+        "protoc": attr.label(
+            default = Label("@com_google_protobuf//:protoc"),
+            executable = True,
+            # `"host"` is the legacy spelling of `"exec"`. Modern
+            # Bazel (>= 6) prefers `"exec"`, but still accepts
+            # `"host"` as an alias through at least Bazel 8.x. We
+            # keep `"host"` here for the broad range of Bazel
+            # versions used to build brpc; switch to `"exec"` once
+            # `"host"` is finally removed (likely a future major
+            # Bazel release).
+            cfg = "host",
+            allow_files = True,
+        ),
+        # Native `proto_library` deps (well-known protos or external
+        # .proto libraries). .proto sources and -I paths are obtained
+        # automatically through Bazel's native `ProtoInfo`, fully
+        # decoupled from the protobuf version.
+        "proto_deps": attr.label_list(
+            providers = [ProtoInfo],
+            default = [],
+        ),
+    },
+)
diff --git a/cmake/CMakeLists.download_gtest.in 
b/cmake/CMakeLists.download_gtest.in
index df020c89..78d99baf 100644
--- a/cmake/CMakeLists.download_gtest.in
+++ b/cmake/CMakeLists.download_gtest.in
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cmake_minimum_required(VERSION 2.8.10)
+cmake_minimum_required(VERSION 2.8.12)
 
 project(googletest-download NONE)
 
diff --git a/docs/cn/bthread_tracer.md b/docs/cn/bthread_tracer.md
index 275585d1..bab09ce4 100644
--- a/docs/cn/bthread_tracer.md
+++ b/docs/cn/bthread_tracer.md
@@ -122,7 +122,7 @@ libunwind 的 `src/unwind/*.c` 实现了 GCC 的 `_Unwind_*` ABI 
兼容层(`_U
 | `cmake` | 从源码编译并安装 libunwind,把头文件与库目录显式传给 `cmake` |
 | `bazel`(Bzlmod) | 直接使用 brpc 仓库自维护的 libunwind 版本 |
 
-### `make`(`config_brpc.sh`)
+### make (config_brpc.sh)
 
 源码编译并安装 libunwind 到一个独立目录(避免污染系统目录),然后让 `config_brpc.sh` 显式从该目录查找 libunwind。
 
@@ -153,7 +153,7 @@ nm -D /libunwind/lib/libunwind.so | grep ' T _Unwind_' \
     || echo "OK: _Unwind_* hidden"
 ```
 
-### `cmake`
+### cmake
 
 [`CMakeLists.txt`](../../CMakeLists.txt:90-100) 通过 `find_library(... NAMES 
unwind unwind-x86_64)` 查找 libunwind。同样需要先源码编译 libunwind 到独立前缀,再用 
`CMAKE_PREFIX_PATH` 让 cmake 优先在该前缀下查找:
 
@@ -173,7 +173,7 @@ make -j$(nproc)
 > `-DLIBUNWIND_LIB=/libunwind/lib/libunwind.so 
 > -DLIBUNWIND_X86_64_LIB=/libunwind/lib/libunwind-x86_64.so 
 > -DLIBUNWIND_INCLUDE_PATH=/libunwind/include`
 > 强制走自编译版本,避免系统包混入。
 
-### `bazel`(Bzlmod)
+### bazel (Bzlmod)
 
 bRPC 仓库已经在 [`registry/modules/libunwind/`](../../registry/modules/libunwind/) 
维护了一份 libunwind 的 Bzlmod overlay,并通过 [`.bazelrc`](../../.bazelrc) 中的 
`--registry=https://github.com/apache/brpc/registry` 使用 bRPC 维护的 overlay。版本号采用 
`<base>.brpc-no-unwind` 后缀(例如 `1.8.3.brpc-no-unwind`),用以区别于 BCR 上的同基版本。该 
overlay 增加了一个开关:
 
diff --git a/example/BUILD.bazel b/example/BUILD.bazel
index df2722a4..4ee7cb14 100644
--- a/example/BUILD.bazel
+++ b/example/BUILD.bazel
@@ -13,8 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_proto_library")
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library")
 
 COPTS = [
     "-D__STDC_FORMAT_MACROS",
@@ -36,32 +36,18 @@ COPTS = [
     "//conditions:default": [""],
 })
 
-proto_library(
-    name = "echo_c++_proto",
-    srcs = [
-        "echo_c++/echo.proto",
-    ],
-)
-
-proto_library(
-    name = "rdma_performance_proto",
-    srcs = [
-        "rdma_performance/test.proto",
-    ],
-)
-
-cc_proto_library(
+brpc_proto_library(
     name = "cc_echo_c++_proto",
-    deps = [
-        ":echo_c++_proto",
-    ],
+    srcs = ["echo_c++/echo.proto"],
+    include = "echo_c++",
+    proto_deps = [],
 )
 
-cc_proto_library(
+brpc_proto_library(
     name = "cc_rdma_performance_proto",
-    deps = [
-        ":rdma_performance_proto",
-    ],
+    srcs = ["rdma_performance/test.proto"],
+    include = "rdma_performance",
+    proto_deps = [],
 )
 
 cc_binary(
diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json 
b/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json
index 3f14a160..aa6ba905 100644
--- a/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json
+++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json
@@ -3,7 +3,7 @@
     "strip_prefix": "libunwind-1.8.3",
     "overlay": {
         "BUILD.bazel": "sha256-ozAABv0I3+tacve4z2m+lAh/lhJWh7t7L3unh8FSfp8=",
-        "MODULE.bazel": "sha256-EtBEsSDMJgdmCs7ByJxVP2SSVc+E7kUVLgpI3TEh/9M="
+        "MODULE.bazel": "sha256-qimDkl8soxNM/JuqndzGOOcn5ZwGvo2J6CSsNS2/IQs="
     },
     "integrity": "sha256-vjDZEOZ/WNgudTIx8TV/MmoaCIrPEmsh/3fmCqsZuQs="
 }
diff --git a/src/bthread/task_tracer.cpp b/src/bthread/task_tracer.cpp
index b550c03a..e6049f0d 100644
--- a/src/bthread/task_tracer.cpp
+++ b/src/bthread/task_tracer.cpp
@@ -17,6 +17,13 @@
 
 #ifdef BRPC_BTHREAD_TRACER
 
+#include <inttypes.h>
+#include <signal.h>
+#include <unistd.h>
+#include <poll.h>
+#include <pthread.h>
+#include <iomanip>
+#include <gflags/gflags.h>
 #include "bthread/task_tracer.h"
 #include "bthread/processor.h"
 #include "bthread/task_group.h"
@@ -25,11 +32,6 @@
 #include "butil/reloadable_flags.h"
 #include "absl/debugging/stacktrace.h"
 #include "absl/debugging/symbolize.h"
-#include <csignal>
-#include <gflags/gflags.h>
-#include <poll.h>
-#include <pthread.h>
-#include <unistd.h>
 
 namespace bthread {
 
@@ -77,7 +79,8 @@ std::string TaskTracer::Result::OutputToString() const {
     char unknown_symbol_name[] = "<unknown>";
     if (frame_count > 0) {
         for (size_t i = 0; i < frame_count; ++i) {
-            butil::string_appendf(&str, "#%zu 0x%16p ", i, ips[i]);
+            butil::string_appendf(&str, "#%zu 0x%016" PRIxPTR " ", i,
+                                  reinterpret_cast<uintptr_t>(ips[i]));
             if (absl::Symbolize(ips[i], symbol_name, arraysize(symbol_name))) {
                 str.append(symbol_name);
             } else {
@@ -103,7 +106,10 @@ void TaskTracer::Result::OutputToStream(std::ostream& os) 
const {
     char unknown_symbol_name[] = "<unknown>";
     if (frame_count > 0) {
         for (size_t i = 0; i < frame_count; ++i) {
-            os << "# " << i << " 0x" << std::hex << ips[i] << std::dec << " ";
+            os << "#" << i << " 0x"
+               << std::hex << std::setw(16) << std::setfill('0')
+               << reinterpret_cast<uintptr_t>(ips[i])
+               << std::dec << std::setfill(' ') << " ";
             if (absl::Symbolize(ips[i], symbol_name, arraysize(symbol_name))) {
                 os << symbol_name;
             } else {
diff --git a/src/butil/endpoint.cpp b/src/butil/endpoint.cpp
index a8d8936c..c5a888b8 100644
--- a/src/butil/endpoint.cpp
+++ b/src/butil/endpoint.cpp
@@ -394,13 +394,38 @@ int endpoint2hostname(const EndPoint& point, std::string* 
host) {
 
 #if defined(OS_LINUX)
 static short epoll_to_poll_events(uint32_t epoll_events) {
-    // Most POLL* and EPOLL* are same values.
+    // Most POLL* and EPOLL* share the same numeric values for the basic
+    // event bits, so a plain mask is enough to translate them.
     short poll_events = (epoll_events &
                          (EPOLLIN | EPOLLPRI | EPOLLOUT |
                           EPOLLRDNORM | EPOLLRDBAND |
                           EPOLLWRNORM | EPOLLWRBAND |
                           EPOLLMSG | EPOLLERR | EPOLLHUP));
-    CHECK_EQ((uint32_t)poll_events, epoll_events);
+    // epoll-only modifier bits (EPOLLET / EPOLLONESHOT / EPOLLRDHUP / ...)
+    // have no poll(2) counterpart and MUST be silently dropped here:
+    //   * poll(2) is already level-triggered and reports events per call,
+    //     so EPOLLET / EPOLLONESHOT degrade naturally to "no-op".
+    //   * `short` cannot even represent EPOLLET (bit 31 = 0x80000000).
+    // Without this filtering, a caller invoking bthread_fd_wait(fd,
+    // EPOLLIN | EPOLLET) from a pthread context would CHECK-fail here.
+    const uint32_t epoll_modifier_bits = 0u
+#ifdef EPOLLET
+        | EPOLLET
+#endif
+#ifdef EPOLLONESHOT
+        | EPOLLONESHOT
+#endif
+#ifdef EPOLLRDHUP
+        | EPOLLRDHUP
+#endif
+#ifdef EPOLLEXCLUSIVE
+        | EPOLLEXCLUSIVE
+#endif
+#ifdef EPOLLWAKEUP
+        | EPOLLWAKEUP
+#endif
+        ;
+    CHECK_EQ((uint32_t)poll_events, epoll_events & ~epoll_modifier_bits);
     return poll_events;
 }
 #elif defined(OS_MACOSX)
diff --git a/src/butil/third_party/symbolize/symbolize.cc 
b/src/butil/third_party/symbolize/symbolize.cc
index 19649b35..0b4000ad 100644
--- a/src/butil/third_party/symbolize/symbolize.cc
+++ b/src/butil/third_party/symbolize/symbolize.cc
@@ -333,10 +333,18 @@ FindSymbol(uint64_t pc, const int fd, char *out, int 
out_size,
 // both regular and dynamic symbol tables if necessary.  On success,
 // write the symbol name to "out" and return true.  Otherwise, return
 // false.
+// `base_address` is the runtime VA that corresponds to ELF VA 0 of the
+// object identified by `fd`. For ET_EXEC binaries it is ignored
+// (symbols already hold absolute runtime addresses); for ET_DYN
+// objects (PIE executables and shared libraries) the caller must
+// supply a precise value (see
+// OpenObjectFileContainingPcAndGetStartAddress() below, which derives
+// it from the object's PT_LOAD program headers rather than from the
+// /proc/self/maps file_offset field).
 static bool GetSymbolFromObjectFile(const int fd, uint64_t pc,
                                     char *out, int out_size,
                                     uint64_t *out_saddr,
-                                    uint64_t map_start_address) {
+                                    uint64_t base_address) {
   // Read the ELF header.
   ElfW(Ehdr) elf_header;
   if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
@@ -345,7 +353,7 @@ static bool GetSymbolFromObjectFile(const int fd, uint64_t 
pc,
 
   uint64_t symbol_offset = 0;
   if (elf_header.e_type == ET_DYN) {  // DSO needs offset adjustment.
-    symbol_offset = map_start_address;
+    symbol_offset = base_address;
   }
 
   ElfW(Shdr) symtab, strtab;
@@ -528,13 +536,26 @@ OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
     return -1;
   }
 
+  // Also open /proc/self/mem so we can read ELF headers / program
+  // headers directly out of the mapped binary, which is the only
+  // reliable way to compute the runtime ELF base address for PIE
+  // executables and shared libraries (the file_offset reported by
+  // /proc/self/maps is per-mapping and does not necessarily match the
+  // ELF base when modern toolchains use `-z separate-code` or when
+  // the first PT_LOAD has a non-zero p_vaddr). Mirrors the
+  // implementation in glog upstream.
+  int mem_fd;
+  NO_INTR(mem_fd = open("/proc/self/mem", O_RDONLY));
+  FileDescriptor wrapped_mem_fd(mem_fd);
+  if (wrapped_mem_fd.get() < 0) {
+    return -1;
+  }
+
   // Iterate over maps and look for the map containing the pc.  Then
   // look into the symbol tables inside.
   char buf[1024];  // Big enough for line of sane /proc/self/maps
-  int num_maps = 0;
   LineReader reader(wrapped_maps_fd.get(), buf, sizeof(buf));
   while (true) {
-    num_maps++;
     const char *cursor;
     const char *eol;
     if (!reader.ReadLine(&cursor, &eol)) {  // EOF or malformed line.
@@ -563,11 +584,6 @@ OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
     }
     ++cursor;  // Skip ' '.
 
-    // Check start and end addresses.
-    if (!(start_address <= pc && pc < end_address)) {
-      continue;  // We skip this map.  PC isn't in this map.
-    }
-
     // Read flags.  Skip flags until we encounter a space or eol.
     const char * const flags_start = cursor;
     while (cursor < eol && *cursor != ' ') {
@@ -578,32 +594,74 @@ OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
       return -1;  // Malformed line.
     }
 
+    // Determine the base address by reading ELF headers in process
+    // memory. We must do this for *every* readable map, not just the
+    // r-x map that contains PC, because a PIE binary or shared
+    // library's ELF header is typically mapped by an earlier r-- map
+    // (separate-code layout). Once we encounter the r-- map carrying
+    // the ELF magic, we record base_address for the whole object.
+    // When we later reach the r-x map containing PC, base_address is
+    // already correct.
+    if (flags_start[0] == 'r') {
+      ElfW(Ehdr) ehdr;
+      if (ReadFromOffsetExact(wrapped_mem_fd.get(), &ehdr, sizeof(ehdr),
+                              start_address) &&
+          memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) {
+        switch (ehdr.e_type) {
+          case ET_EXEC:
+            base_address = 0;
+            break;
+          case ET_DYN:
+            // Find the PT_LOAD segment with p_offset == 0 (i.e. the
+            // segment that contains the ELF header). Its p_vaddr is
+            // the ELF VA that corresponds to the bytes we just read
+            // at `start_address`, so base_address = start_address -
+            // p_vaddr. Normally p_vaddr is 0 and base_address ==
+            // start_address, but some non-standard linker scripts
+            // place the first LOAD at a non-zero VA. Fall back to
+            // start_address if no such PT_LOAD is found.
+            base_address = start_address;
+            for (unsigned i = 0; i != ehdr.e_phnum; ++i) {
+              ElfW(Phdr) phdr;
+              if (ReadFromOffsetExact(wrapped_mem_fd.get(), &phdr,
+                                      sizeof(phdr),
+                                      start_address + ehdr.e_phoff +
+                                          i * sizeof(phdr)) &&
+                  phdr.p_type == PT_LOAD && phdr.p_offset == 0) {
+                base_address = start_address - phdr.p_vaddr;
+                break;
+              }
+            }
+            break;
+          default:
+            // ET_REL or ET_CORE. Not directly executable, leave
+            // base_address untouched.
+            break;
+        }
+      }
+    }
+
+    // Check start and end addresses.
+    if (!(start_address <= pc && pc < end_address)) {
+      continue;  // We skip this map.  PC isn't in this map.
+    }
+
     // Check flags.  We are only interested in "r-x" maps.
     if (memcmp(flags_start, "r-x", 3) != 0) {  // Not a "r-x" map.
       continue;  // We skip this map.
     }
     ++cursor;  // Skip ' '.
 
-    // Read file offset.
+    // Read file offset (parsed but no longer used for base_address;
+    // base_address is now computed from PT_LOAD program headers
+    // above. Keep the parse so the cursor advances to the file name).
     uint64_t file_offset;
     cursor = GetHex(cursor, eol, &file_offset);
     if (cursor == eol || *cursor != ' ') {
       return -1;  // Malformed line.
     }
     ++cursor;  // Skip ' '.
-
-    // Don't subtract 'start_address' from the first entry:
-    // * If a binary is compiled w/o -pie, then the first entry in
-    //   process maps is likely the binary itself (all dynamic libs
-    //   are mapped higher in address space). For such a binary,
-    //   instruction offset in binary coincides with the actual
-    //   instruction address in virtual memory (as code section
-    //   is mapped to a fixed memory range).
-    // * If a binary is compiled with -pie, all the modules are
-    //   mapped high at address space (in particular, higher than
-    //   shadow memory of the tool), so the module can't be the
-    //   first entry.
-    base_address = ((num_maps == 1) ? 0U : start_address) - file_offset;
+    (void)file_offset;
 
     // Skip to file name.  "cursor" now points to dev.  We need to
     // skip at least two spaces for dev and inode.
@@ -794,9 +852,18 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void 
*pc, char *out,
       out_size -= num_bytes_written;
     }
   }
+  // Use `base_address` (computed by
+  // OpenObjectFileContainingPcAndGetStartAddress() via the object's
+  // PT_LOAD program headers) as the relocation offset, NOT
+  // `start_address`. For ET_DYN objects produced by modern toolchains
+  // (binutils >= 2.31, lld) using `-z separate-code`, the r-x mapping
+  // starts at a non-zero file offset and `start_address` no longer
+  // equals the ELF base; only `base_address` is the correct value
+  // such that `symbol.st_value + base_address` recovers the runtime
+  // VA of a symbol.
   if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0,
                                out, out_size, out_saddr,
-                               start_address)) {
+                               base_address)) {
     return false;
   }
 
diff --git a/src/bvar/detail/percentile.h b/src/bvar/detail/percentile.h
index bac729f1..e6acbf3a 100644
--- a/src/bvar/detail/percentile.h
+++ b/src/bvar/detail/percentile.h
@@ -561,7 +561,23 @@ public:
     typedef VoidOp InvOp;
     typedef ReducerSampler<Percentile, value_type, Op, InvOp> sampler_type;
 
-    Percentile() = default;
+    Percentile() {
+        // babylon::ConcurrentSampler defaults every per-value-range
+        // bucket to capacity 30 (see _bucket_capacity[32] in
+        // babylon/concurrent/counter.h) and only grows it inside
+        // Percentile::reset() after observing how many samples
+        // arrived. That means the *first* collection round always
+        // observes badly under-sampled buckets, which destabilises
+        // mid-quantile estimates (e.g. p60/p70 on a uniform 1..10000
+        // stream can land at ~5482/~6284 instead of ~6000/~7000 and
+        // make PercentileTest.add fail). Pre-size every bucket to the
+        // full SAMPLE_SIZE on construction so the very first reset()
+        // already sees representative samples.
+        for (size_t i = 0; i < NUM_INTERVALS; ++i) {
+            _concurrent_sampler.set_bucket_capacity(i, 
value_type::SAMPLE_SIZE);
+        }
+    }
+
     DISALLOW_COPY_AND_MOVE(Percentile);
     ~Percentile() noexcept {
         if (NULL != _sampler) {
diff --git a/test/BUILD.bazel b/test/BUILD.bazel
index b68b3fa0..18af200d 100644
--- a/test/BUILD.bazel
+++ b/test/BUILD.bazel
@@ -13,30 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library", "cc_test")
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library")
 load("@hedron_compile_commands//:refresh_compile_commands.bzl", 
"refresh_compile_commands")
+load("//test:generate_unittests.bzl", "generate_unittests")
+load("//test:root_runfiles.bzl", "root_runfiles")
 
 COPTS = [
-    "-D__STDC_FORMAT_MACROS",
-    "-DBTHREAD_USE_FAST_PTHREAD_MUTEX",
-    "-D__const__=__unused__",
-    "-D_GNU_SOURCE",
-    "-DUSE_SYMBOLIZE",
-    "-DNO_TCMALLOC",
-    "-D__STDC_LIMIT_MACROS",
-    "-D__STDC_CONSTANT_MACROS",
     "-fPIC",
     "-Wno-unused-parameter",
     "-fno-omit-frame-pointer",
     "-fno-access-control",
-    "-DBAZEL_TEST=1",
-    "-DBVAR_NOT_LINK_DEFAULT_VARIABLES",
-    "-DUNIT_TEST",
-] + select({
-    "//bazel/config:brpc_with_glog": ["-DBRPC_WITH_GLOG=1"],
-    "//conditions:default": ["-DBRPC_WITH_GLOG=0"],
-})
+]
 
 TEST_BUTIL_SOURCES = [
     "at_exit_unittest.cc",
@@ -148,26 +136,11 @@ TEST_BUTIL_SOURCES = [
     ],
 })
 
-proto_library(
-    name = "test_proto",
-    srcs = glob(
-        [
-            "*.proto",
-        ],
-    ),
-    strip_import_prefix = "/test",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//:brpc_idl_options_proto",
-    ]
-)
-
-cc_proto_library(
+brpc_proto_library(
     name = "cc_test_proto",
+    srcs = glob(["*.proto"]),
+    deps = ["//:brpc_idl_options_cc_proto"],
     visibility = ["//visibility:public"],
-    deps = [
-        ":test_proto",
-    ],
 )
 
 cc_library(
@@ -184,8 +157,22 @@ cc_library(
     ],
 )
 
+# tcmalloc and AddressSanitizer both intercept malloc/free, so linking
+# both produces undefined behaviour at runtime (typically a hard hang
+# at process start, or random allocator errors). When `brpc_with_asan`
+# is active we drop the @gperftools//:tcmalloc dep entirely; the
+# gperftools_helper header above already no-ops gracefully when
+# BRPC_ENABLE_CPU_PROFILER is not defined.
+#
+# Activation: `--config=asan` in .bazelrc also sets
+# `--define=with_asan=true`, which flips this config_setting.
+TCMALLOC_DEP_UNLESS_ASAN = select({
+    "//bazel/config:brpc_with_asan": [],
+    "//conditions:default": ["@gperftools//:tcmalloc"],
+})
+
 cc_test(
-    name = "butil_test",
+    name = "butil_unittests",
     srcs = TEST_BUTIL_SOURCES + [
         "scoped_locale.h",
         "multiprocess_func_list.h",
@@ -196,104 +183,73 @@ cc_test(
         ":cc_test_proto",
         ":sstream_workaround",
         ":gperftools_helper",
-        "//:brpc",
+        "//:butil",
+        "//:bthread",
         "@com_google_googletest//:gtest",
     ],
 )
 
-
 cc_test(
-    name = "bvar_test",
-    srcs = glob(
-        [
-            "bvar_*_unittest.cpp",
-        ],
-        exclude = [
-            "bvar_lock_timer_unittest.cpp",
-            "bvar_recorder_unittest.cpp",
-        ],
-    ),
-    copts = COPTS,
+    name = "bvar_unittests",
+    srcs = glob(["bvar_*_unittest.cpp"]),
     deps = [
         ":sstream_workaround",
         ":gperftools_helper",
         "//:bvar",
         "@com_google_googletest//:gtest",
-    ],
+        "@com_google_googletest//:gtest_main",
+    ] + TCMALLOC_DEP_UNLESS_ASAN,
+    copts = COPTS,
 )
 
-cc_test(
-    name = "bthread_test",
-    srcs = glob(
-        [
-            "bthread_*_unittest.cpp",
-        ],
-        exclude = [
-            "bthread_cond_unittest.cpp",
-            "bthread_execution_queue_unittest.cpp",
-            "bthread_dispatcher_unittest.cpp",
-            "bthread_fd_unittest.cpp",
-            "bthread_mutex_unittest.cpp",
-            "bthread_setconcurrency_unittest.cpp",
-            # glog CHECK die with a fatal error
-            "bthread_key_unittest.cpp",
-            "bthread_butex_multi_tag_unittest.cpp",
-            "bthread_rwlock_unittest.cpp",
-            "bthread_semaphore_unittest.cpp",
-        ],
-    ),
-    copts = COPTS,
+generate_unittests(
+    name = "bthread_unittests",
+    srcs = glob([
+        "bthread*_unittest.cpp",
+    ]),
     deps = [
         ":sstream_workaround",
         ":gperftools_helper",
-        "//:brpc",
+        "//:bthread",
         "@com_google_googletest//:gtest",
         "@com_google_googletest//:gtest_main",
-    ],
-)
-
-cc_test(
-    name = "brpc_prometheus_test",
-    srcs = glob(
-        [
-            "brpc_prometheus_*_unittest.cpp",
-        ],
-    ),
+    ] + TCMALLOC_DEP_UNLESS_ASAN,
     copts = COPTS,
-    deps = [
-        ":cc_test_proto",
-        ":sstream_workaround",
-        "//:brpc",
-        "@com_google_googletest//:gtest",
-        "@com_google_googletest//:gtest_main",
-    ],
 )
 
-cc_test(
-    name = "brpc_auto_concurrency_limiter_test",
+# Expose unit-test data files (cert*, jsonout) at the runfiles workspace root
+# so that tests can open them via plain relative paths like "cert1.crt".
+# See test/root_runfiles.bzl for why a genrule does NOT work here.
+root_runfiles(
+    name = "test_runfiles_root_data",
     srcs = [
-        "brpc_auto_concurrency_limiter_unittest.cpp",
-    ],
-    copts = COPTS,
-    deps = [
-        "//:brpc",
-        "@com_google_googletest//:gtest",
-        "@com_google_googletest//:gtest_main",
+        "cert1.crt",
+        "cert1.key",
+        "cert2.crt",
+        "cert2.key",
+        "jsonout",
     ],
 )
 
-cc_test(
-    name = "brpc_redis_cluster_test",
-    srcs = [
-        "brpc_redis_cluster_unittest.cpp",
-    ],
-    copts = COPTS,
+generate_unittests(
+    name = "brpc_unittests",
+    srcs = glob([
+        "brpc_*_unittest.cpp",
+    ]),
     deps = [
         ":sstream_workaround",
         ":gperftools_helper",
         "//:brpc",
+        ":cc_test_proto",
         "@com_google_googletest//:gtest",
         "@com_google_googletest//:gtest_main",
+    ] + TCMALLOC_DEP_UNLESS_ASAN,
+    copts = COPTS,
+    # Place cert*/jsonout at <runfiles>/_main/<file> (the cwd of `bazel test`)
+    # via :test_runfiles_root_data so test code can open them with plain
+    # relative paths like "cert1.crt".
+    data = [
+        ":test_runfiles_root_data",
     ],
 )
 
@@ -303,9 +259,10 @@ refresh_compile_commands(
     # For example, specify a dict of targets and their arguments:
     targets = {
         "//:brpc": "",
-        ":bvar_test": "",
-        ":bthread_test": "",
-        ":butil_test": "",
+        ":butil_unittests": "",
+        ":bvar_unittests": "",
+        ":bthread_unittests": "",
+        ":brpc_unittests": "",
     },
     # For more details, feel free to look into refresh_compile_commands.bzl if 
you want.
 )
diff --git a/test/brpc_alpn_protocol_unittest.cpp 
b/test/brpc_alpn_protocol_unittest.cpp
index 21aada70..9c150671 100644
--- a/test/brpc_alpn_protocol_unittest.cpp
+++ b/test/brpc_alpn_protocol_unittest.cpp
@@ -48,7 +48,7 @@ public:
         response->set_message(request->message());
 
         brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
-        LOG(NOTICE) << "protocol:" << cntl->request_protocol();
+        LOG(INFO) << "protocol:" << cntl->request_protocol();
   }
 };
 
@@ -65,9 +65,9 @@ public:
         ssl_options->default_cert.private_key = "cert1.key";
         ssl_options->alpns = "http, h2, baidu_std";
 
-        EXPECT_EQ(0, _server.AddService(&_echo_server_impl,
+        ASSERT_EQ(0, _server.AddService(&_echo_server_impl,
                                         brpc::SERVER_DOESNT_OWN_SERVICE));
-        EXPECT_EQ(0, _server.Start(FLAGS_listen_addr.data(), &server_options));
+        ASSERT_EQ(0, _server.Start(FLAGS_listen_addr.data(), &server_options));
     }
     
     virtual void TearDown() override {
diff --git a/test/brpc_http_rpc_protocol_unittest.cpp 
b/test/brpc_http_rpc_protocol_unittest.cpp
index e39ac39d..c7022ed2 100644
--- a/test/brpc_http_rpc_protocol_unittest.cpp
+++ b/test/brpc_http_rpc_protocol_unittest.cpp
@@ -20,7 +20,6 @@
 // Date: Sun Jul 13 15:04:18 CST 2014
 
 #include <cstddef>
-#include <google/protobuf/stubs/logging.h>
 #include <string>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -1278,7 +1277,7 @@ public:
 private:
     void check_header(brpc::Controller* cntl) {
         const std::string* test_header = 
cntl->http_request().GetHeader(TEST_PROGRESSIVE_HEADER);
-        GOOGLE_CHECK_NOTNULL(test_header);
+        CHECK(test_header != NULL);
         CHECK_EQ(*test_header, TEST_PROGRESSIVE_HEADER_VAL);
     }
 };
@@ -1958,9 +1957,11 @@ TEST_F(HttpTest, proto_text_content_type) {
     cntl.http_request().set_method(brpc::HTTP_METHOD_POST);
     cntl.http_request().uri() = "/EchoService/Echo";
     cntl.http_request().set_content_type("application/proto-text");
-    cntl.request_attachment().append(req.Utf8DebugString());
+    std::string req_text;
+    ASSERT_TRUE(google::protobuf::TextFormat::PrintToString(req, &req_text));
+    cntl.request_attachment().append(req_text);
     channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr);
-    ASSERT_FALSE(cntl.Failed());
+    ASSERT_FALSE(cntl.Failed()) << req_text;
     ASSERT_EQ("application/proto-text", cntl.http_response().content_type());
     ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(
             cntl.response_attachment().to_string(), &res));
diff --git a/test/brpc_protobuf_json_unittest.cpp 
b/test/brpc_protobuf_json_unittest.cpp
index b9289b20..aa73fbd8 100644
--- a/test/brpc_protobuf_json_unittest.cpp
+++ b/test/brpc_protobuf_json_unittest.cpp
@@ -526,7 +526,12 @@ TEST_F(ProtobufJsonTest, json_to_pb_unbounded_recursion) {
         std::string error;
         bool ret = json2pb::ProtoJsonToProtoMessage(nested_json, &msg, 
options, &error);
         ASSERT_FALSE(ret);
-        ASSERT_EQ("INVALID_ARGUMENT:Message too deep. Max recursion depth 
reached for key 'child'", error);
+        ASSERT_NE(std::string::npos, error.find("INVALID_ARGUMENT"))
+            << "error=" << error;
+        ASSERT_TRUE(error.find("recursion") != std::string::npos ||
+                    error.find("nested") != std::string::npos ||
+                    error.find("too deep") != std::string::npos)
+            << "error=" << error;
     }
 }
 
diff --git a/test/brpc_streaming_rpc_unittest.cpp 
b/test/brpc_streaming_rpc_unittest.cpp
index 0f8a3e56..6a3b7b9e 100644
--- a/test/brpc_streaming_rpc_unittest.cpp
+++ b/test/brpc_streaming_rpc_unittest.cpp
@@ -633,7 +633,9 @@ TEST_F(StreamingRpcTest, failed_when_rst) {
         ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
     }
 
-    usleep(1000 * 10);
+    while (handler._expected_next_value != N) {
+        usleep(100);
+    }
     {
         brpc::SocketUniquePtr ptr;
         ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr));
diff --git a/test/bthread_mutex_unittest.cpp b/test/bthread_mutex_unittest.cpp
index f839d063..121f1ebb 100644
--- a/test/bthread_mutex_unittest.cpp
+++ b/test/bthread_mutex_unittest.cpp
@@ -328,7 +328,6 @@ TEST(MutexTest, fast_pthread_mutex) {
 void* do_pthread_timedlock(void *arg) {
     struct timespec t = { -2, 0 };
     EXPECT_EQ(ETIMEDOUT, pthread_mutex_timedlock((pthread_mutex_t*)arg, &t));
-    EXPECT_EQ(ETIMEDOUT, errno);
     return NULL;
 }
 #endif
@@ -347,7 +346,7 @@ TEST(MutexTest, pthread_mutex) {
         struct timespec t = { -2, 0 };
         ASSERT_EQ(ETIMEDOUT, pthread_mutex_timedlock(&mutex, &t));
         pthread_t th;
-        ASSERT_EQ(0, pthread_create(&th, NULL, do_fast_pthread_timedlock, 
&mutex));
+        ASSERT_EQ(0, pthread_create(&th, NULL, do_pthread_timedlock, &mutex));
         ASSERT_EQ(0, pthread_join(th, NULL));
 #endif
     }
diff --git a/cmake/CMakeLists.download_gtest.in b/test/generate_unittests.bzl
similarity index 63%
copy from cmake/CMakeLists.download_gtest.in
copy to test/generate_unittests.bzl
index df020c89..4b37dd6f 100644
--- a/cmake/CMakeLists.download_gtest.in
+++ b/test/generate_unittests.bzl
@@ -13,18 +13,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cmake_minimum_required(VERSION 2.8.10)
+def generate_unittests(name, srcs, deps, copts, linkopts = [], data = []):
+    tests = []
+    for s in srcs:
+        ut_name = s.replace(".cpp", "")
+        native.cc_test(
+            name = ut_name,
+            srcs = [s],
+            copts = copts,
+            deps = deps,
+            linkopts = linkopts,
+            data = data,
+        )
+        tests.append(":" + ut_name)
 
-project(googletest-download NONE)
-
-include(ExternalProject)
-ExternalProject_Add(googletest
-  GIT_REPOSITORY    https://github.com/google/googletest.git
-  GIT_TAG           release-1.12.0
-  SOURCE_DIR        "${PROJECT_BINARY_DIR}/googletest-src"
-  BINARY_DIR        "${PROJECT_BINARY_DIR}/googletest-build"
-  CONFIGURE_COMMAND ""
-  BUILD_COMMAND     ""
-  INSTALL_COMMAND   ""
-  TEST_COMMAND      ""
-)
+    native.test_suite(
+        name  = name,
+        tests = tests,
+    )
\ No newline at end of file
diff --git a/test/root_runfiles.bzl b/test/root_runfiles.bzl
new file mode 100644
index 00000000..7d38b87d
--- /dev/null
+++ b/test/root_runfiles.bzl
@@ -0,0 +1,79 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A small Starlark rule that exposes files at the *runfiles workspace root*.
+
+Why this exists
+---------------
+`bazel test` sets the test process cwd to `${TEST_SRCDIR}/${TEST_WORKSPACE}`,
+which under bzlmod is `<test.runfiles>/_main/` -- the workspace root inside
+the runfiles tree. Files declared via the standard `data` attribute on a
+target in `//test` are placed under `_main/test/<file>` (the rule's package
+path), which is NOT the cwd.
+
+Several brpc tests (e.g. brpc_alpn_protocol_unittest, brpc_ssl_unittest,
+brpc_protobuf_json_unittest) open data files such as `cert1.crt`, `cert1.key`,
+`jsonout` by *plain relative paths* (this works under the CMake build because
+file(COPY ...) places them next to the test binary). To make those same
+relative paths work under Bazel without rewriting the test code, we need the
+files to appear at `_main/<file>`, i.e. directly at the cwd.
+
+A genrule cannot help here, because its `outs` must live in the genrule's own
+package. The clean way is `ctx.runfiles(symlinks = {...})`, which places the
+given files at arbitrary paths *relative to the workspace root inside the
+runfiles tree* -- which is exactly the cwd of a Bazel-launched test.
+
+Beware: `ctx.runfiles` has TWO related dict parameters:
+  * symlinks      -> paths are relative to <runfiles>/<workspace>/ (the cwd)
+  * root_symlinks -> paths are relative to <runfiles>/ (one level above cwd)
+We want the first one.
+
+Usage
+-----
+    load("//test:root_runfiles.bzl", "root_runfiles")
+
+    root_runfiles(
+        name = "test_runfiles_root_data",
+        srcs = [
+            "cert1.crt",
+            "cert1.key",
+            "jsonout",
+        ],
+    )
+
+Then add `:test_runfiles_root_data` to a cc_test's `data` attribute.
+"""
+
+def _root_runfiles_impl(ctx):
+    # Map each input file to its basename, placed at the workspace root inside
+    # the runfiles tree. That root is the cwd of a `bazel test` process, so
+    # tests can open the files via plain relative paths like "cert1.crt".
+    symlinks = {f.basename: f for f in ctx.files.srcs}
+    runfiles = ctx.runfiles(symlinks = symlinks)
+    return [DefaultInfo(runfiles = runfiles)]
+
+root_runfiles = rule(
+    implementation = _root_runfiles_impl,
+    attrs = {
+        "srcs": attr.label_list(
+            allow_files = True,
+            doc = "Files to expose at the workspace root inside the runfiles " 
+
+                  "tree (i.e. the cwd of `bazel test`), keyed by basename.",
+        ),
+    },
+    doc = "Exposes data files at the workspace root inside the runfiles tree " 
+
+          "(the cwd of `bazel test`), so tests can open them via plain " +
+          "relative paths.",
+)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to