From 678b834cb83569256e8bc6f2335681f149ce804e Mon Sep 17 00:00:00 2001 From: shuke <37901441+shuke987@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:36:24 +0800 Subject: [PATCH 1/2] [test](fe) Isolate cloud frontend service test Issue Number: None Related PR: None Problem Summary: FrontendServiceImplTest starts a non-cloud mocked FE and BE. testGetTabletReplicaInfosNullJobReturnsCancelledWithoutNpe temporarily enabled cloud mode through Config.cloud_unique_id, allowing the shared background heartbeat thread to take a cloud-only path and mark the mock BE dead. Move this cloud-specific test into a dedicated class without the mocked FE/BE fixture, while preserving the same getTabletReplicaInfos cancellation assertions. None - Test: Unit Test - ./run-fe-ut.sh --run org.apache.doris.service.FrontendServiceImplCloudTest - ./run-fe-ut.sh --run org.apache.doris.service.FrontendServiceImplTest - Behavior changed: No - Does this need documentation: No (cherry picked from commit 666eec936cff4335d1b74ca633a423a476062f15) --- .../service/FrontendServiceImplCloudTest.java | 78 +++++++++++++++++++ .../service/FrontendServiceImplTest.java | 39 ---------- 2 files changed, 78 insertions(+), 39 deletions(-) create mode 100644 fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java diff --git a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java new file mode 100644 index 00000000000000..a5a28080315ca9 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java @@ -0,0 +1,78 @@ +// 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. + +package org.apache.doris.service; + +import org.apache.doris.catalog.Env; +import org.apache.doris.cloud.CacheHotspotManager; +import org.apache.doris.cloud.catalog.CloudEnv; +import org.apache.doris.common.Config; +import org.apache.doris.thrift.TGetTabletReplicaInfosRequest; +import org.apache.doris.thrift.TGetTabletReplicaInfosResult; +import org.apache.doris.thrift.TStatusCode; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.Collections; + +public class FrontendServiceImplCloudTest { + + // Regression test for FrontendServiceImpl.getTabletReplicaInfos NPE: + // When a warm-up job has been removed from + // CacheHotspotManager.cloudWarmUpJobs (past + // history_cloud_warm_up_job_keep_max_second), getCloudWarmUpJob + // returns null. The previous code called job.getJobId() inside the + // log message, throwing NPE which bubbled up to BE as + // "Internal error processing getTabletReplicaInfos". + @Test + public void testGetTabletReplicaInfosNullJobReturnsCancelledWithoutNpe() { + String originalCloudUniqueId = Config.cloud_unique_id; + Config.cloud_unique_id = "gettabletreplicainfostest"; + + CloudEnv cloudEnv = Mockito.mock(CloudEnv.class); + CacheHotspotManager cacheHotspotManager = Mockito.mock(CacheHotspotManager.class); + Mockito.when(cloudEnv.getCacheHotspotMgr()).thenReturn(cacheHotspotManager); + // Simulate job already removed from cloudWarmUpJobs. + Mockito.when(cacheHotspotManager.getCloudWarmUpJob(123456L)).thenReturn(null); + + try (MockedStatic envMock = Mockito.mockStatic(Env.class)) { + envMock.when(Env::getCurrentEnv).thenReturn(cloudEnv); + + FrontendServiceImpl frontendService = new FrontendServiceImpl(Mockito.mock(ExecuteEnv.class)); + TGetTabletReplicaInfosRequest request = new TGetTabletReplicaInfosRequest(); + request.setTabletIds(Collections.singletonList(789L)); + request.setWarmUpJobId(123456L); + + TGetTabletReplicaInfosResult result; + try { + result = frontendService.getTabletReplicaInfos(request); + } catch (NullPointerException e) { + throw new AssertionError("getTabletReplicaInfos must not NPE when the " + + "warm-up job has been removed from CacheHotspotManager", e); + } + + Assert.assertNotNull("result.status must be set", result.getStatus()); + Assert.assertEquals("BE must be told to cancel its stale warm-up job entry", + TStatusCode.CANCELLED, result.getStatus().getStatusCode()); + } finally { + Config.cloud_unique_id = originalCloudUniqueId; + } + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java index 2e2f0eec947ccd..5b6f00a96102bf 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java @@ -22,8 +22,6 @@ import org.apache.doris.catalog.OlapTable; import org.apache.doris.catalog.Partition; import org.apache.doris.catalog.TableIf; -import org.apache.doris.cloud.CacheHotspotManager; -import org.apache.doris.cloud.catalog.CloudEnv; import org.apache.doris.common.Config; import org.apache.doris.common.FeConstants; import org.apache.doris.datasource.InternalCatalog; @@ -43,8 +41,6 @@ import org.apache.doris.thrift.TGetDbsResult; import org.apache.doris.thrift.TGetTablesParams; import org.apache.doris.thrift.TGetTablesResult; -import org.apache.doris.thrift.TGetTabletReplicaInfosRequest; -import org.apache.doris.thrift.TGetTabletReplicaInfosResult; import org.apache.doris.thrift.TListTableStatusResult; import org.apache.doris.thrift.TMetadataTableRequestParams; import org.apache.doris.thrift.TMetadataType; @@ -64,7 +60,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.mockito.MockedStatic; import org.mockito.Mockito; import java.util.ArrayList; @@ -446,38 +441,4 @@ public void testShowUser() { System.out.println(result); } - @Test - public void testGetTabletReplicaInfosNullJobReturnsCancelledWithoutNpe() { - String originalCloudUniqueId = Config.cloud_unique_id; - Config.cloud_unique_id = "gettabletreplicainfostest"; - - CloudEnv cloudEnv = Mockito.mock(CloudEnv.class); - CacheHotspotManager cacheHotspotManager = Mockito.mock(CacheHotspotManager.class); - Mockito.when(cloudEnv.getCacheHotspotMgr()).thenReturn(cacheHotspotManager); - Mockito.when(cacheHotspotManager.getCloudWarmUpJob(123456L)).thenReturn(null); - - MockedStatic envMock = Mockito.mockStatic(Env.class); - try { - envMock.when(Env::getCurrentEnv).thenReturn(cloudEnv); - - FrontendServiceImpl frontendService = new FrontendServiceImpl(exeEnv); - TGetTabletReplicaInfosRequest request = new TGetTabletReplicaInfosRequest(); - request.setTabletIds(Collections.singletonList(789L)); - request.setWarmUpJobId(123456L); - - TGetTabletReplicaInfosResult result; - try { - result = frontendService.getTabletReplicaInfos(request); - } catch (NullPointerException e) { - throw new AssertionError("getTabletReplicaInfos must not NPE when the " - + "warm-up job has been removed from CacheHotspotManager", e); - } - - Assert.assertNotNull(result.getStatus()); - Assert.assertEquals(TStatusCode.CANCELLED, result.getStatus().getStatusCode()); - } finally { - envMock.close(); - Config.cloud_unique_id = originalCloudUniqueId; - } - } } From e1db953df2729cc3dcfa9dc7dfbb24de4c346c8e Mon Sep 17 00:00:00 2001 From: Refrain Date: Tue, 4 Aug 2026 14:12:26 +0800 Subject: [PATCH 2/2] [test](fe) Mock Thrift context in cloud service test ### What problem does this PR solve? Issue Number: None Related PR: #65577 Problem Summary: The branch-4.0 backport moves the cloud-specific getTabletReplicaInfos test out of FrontendServiceImplTest so it cannot disturb the shared mock backend. Unlike master, branch-4.0 initializes ThriftServerEventProcessor connection context only when a Thrift server is constructed. The standalone test therefore failed before reaching the removed warm-up job path. Mock the absent connection context within the test so it preserves the CANCELLED response coverage without changing production behavior. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-fe-ut.sh --run org.apache.doris.service.FrontendServiceImplCloudTest,org.apache.doris.service.FrontendServiceImplTest - Behavior changed: No - Does this need documentation: No --- .../apache/doris/service/FrontendServiceImplCloudTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java index a5a28080315ca9..5049ed54ea9df4 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplCloudTest.java @@ -21,6 +21,7 @@ import org.apache.doris.cloud.CacheHotspotManager; import org.apache.doris.cloud.catalog.CloudEnv; import org.apache.doris.common.Config; +import org.apache.doris.common.ThriftServerEventProcessor; import org.apache.doris.thrift.TGetTabletReplicaInfosRequest; import org.apache.doris.thrift.TGetTabletReplicaInfosResult; import org.apache.doris.thrift.TStatusCode; @@ -52,8 +53,11 @@ public void testGetTabletReplicaInfosNullJobReturnsCancelledWithoutNpe() { // Simulate job already removed from cloudWarmUpJobs. Mockito.when(cacheHotspotManager.getCloudWarmUpJob(123456L)).thenReturn(null); - try (MockedStatic envMock = Mockito.mockStatic(Env.class)) { + try (MockedStatic envMock = Mockito.mockStatic(Env.class); + MockedStatic eventProcessorMock = + Mockito.mockStatic(ThriftServerEventProcessor.class)) { envMock.when(Env::getCurrentEnv).thenReturn(cloudEnv); + eventProcessorMock.when(ThriftServerEventProcessor::getConnectionContext).thenReturn(null); FrontendServiceImpl frontendService = new FrontendServiceImpl(Mockito.mock(ExecuteEnv.class)); TGetTabletReplicaInfosRequest request = new TGetTabletReplicaInfosRequest();