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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9dd68334c5d [test](fe) Cover internal query audit failure in FE unit 
test (#65696)
9dd68334c5d is described below

commit 9dd68334c5d83ff1dd5add93346763133e37f416
Author: yujun <[email protected]>
AuthorDate: Fri Jul 17 12:15:09 2026 +0800

    [test](fe) Cover internal query audit failure in FE unit test (#65696)
    
    ### What problem does this PR solve?
    
    The previous regression coverage for internal query audit failures
    depended on querying `__internal_schema.audit_log` after a
    fault-injection failure. That path is timing-sensitive because audit
    persistence is asynchronous, so the regression can fail even when
    `StmtExecutor.executeInternalQuery()` already reports the error audit
    event correctly.
    
    This PR replaces that flaky coverage with a deterministic FE unit test.
    The new test forces a planning failure in `executeInternalQuery()` and
    verifies that the submitted `AuditEvent` is marked as `ERR` and carries
    the failure details. The unstable fault-injection regression case is
    removed.
---
 .../doris/qe/StmtExecutorInternalQueryTest.java    | 52 +++++++++++-
 .../test_audit_log_internal_query_failure.groovy   | 98 ----------------------
 2 files changed, 50 insertions(+), 100 deletions(-)

diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/qe/StmtExecutorInternalQueryTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/qe/StmtExecutorInternalQueryTest.java
index a167c1b3ade..ee2b87d1abf 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/qe/StmtExecutorInternalQueryTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/qe/StmtExecutorInternalQueryTest.java
@@ -18,12 +18,17 @@
 package org.apache.doris.qe;
 
 import org.apache.doris.analysis.StatementBase;
+import org.apache.doris.catalog.Env;
 import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.nereids.NereidsPlanner;
+import org.apache.doris.plugin.AuditEvent;
+import org.apache.doris.resource.workloadschedpolicy.WorkloadRuntimeStatusMgr;
 import org.apache.doris.thrift.TQueryOptions;
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockedConstruction;
 import org.mockito.Mockito;
 
@@ -33,6 +38,7 @@ public class StmtExecutorInternalQueryTest {
         StmtExecutor executor = new StmtExecutor(new ConnectContext(), "select 
* from table1");
         try (MockedConstruction<NereidsPlanner> mocked = 
Mockito.mockConstruction(NereidsPlanner.class,
                 (mock, context) -> {
+                    
Mockito.when(mock.getStatementContext()).thenReturn(executor.getContext().getStatementContext());
                     Mockito.doThrow(new RuntimeException()).when(mock).plan(
                             Mockito.any(StatementBase.class), 
Mockito.any(TQueryOptions.class));
                 })) {
@@ -53,8 +59,11 @@ public class StmtExecutorInternalQueryTest {
         ConnectContext ctx = new ConnectContext();
         StmtExecutor executor = new StmtExecutor(ctx, "select * from table1");
         try (MockedConstruction<NereidsPlanner> mocked = 
Mockito.mockConstruction(NereidsPlanner.class,
-                (mock, context) -> Mockito.doThrow(new RuntimeException("mock 
plan failure"))
-                        .when(mock).plan(Mockito.any(StatementBase.class), 
Mockito.any(TQueryOptions.class)))) {
+                (mock, context) -> {
+                    
Mockito.when(mock.getStatementContext()).thenReturn(ctx.getStatementContext());
+                    Mockito.doThrow(new RuntimeException("mock plan failure"))
+                            .when(mock).plan(Mockito.any(StatementBase.class), 
Mockito.any(TQueryOptions.class));
+                })) {
             Assert.assertThrows(RuntimeException.class, 
executor::executeInternalQuery);
         }
         Assert.assertEquals(QueryState.MysqlStateType.ERR, 
ctx.getState().getStateType());
@@ -67,4 +76,43 @@ public class StmtExecutorInternalQueryTest {
         Assert.assertTrue("internal query should be flagged as query in audit 
state",
                 ctx.getState().isQuery());
     }
+
+    @Test
+    public void testExecuteInternalQuerySubmitsErrorAuditEventOnFailure() {
+        ConnectContext ctx = new ConnectContext();
+        StmtExecutor executor = new StmtExecutor(ctx, "select * from table1");
+        Env env = Env.getCurrentEnv();
+        WorkloadRuntimeStatusMgr originalWorkloadRuntimeStatusMgr = 
env.getWorkloadRuntimeStatusMgr();
+        WorkloadRuntimeStatusMgr workloadRuntimeStatusMgr = 
Mockito.mock(WorkloadRuntimeStatusMgr.class);
+        ArgumentCaptor<AuditEvent> auditEventCaptor = 
ArgumentCaptor.forClass(AuditEvent.class);
+
+        Deencapsulation.setField(env, "workloadRuntimeStatusMgr", 
workloadRuntimeStatusMgr);
+        try {
+            try (MockedConstruction<NereidsPlanner> mockedPlanner = 
Mockito.mockConstruction(NereidsPlanner.class,
+                    (mock, context) -> {
+                        
Mockito.when(mock.getStatementContext()).thenReturn(ctx.getStatementContext());
+                        Mockito.doThrow(new RuntimeException("mock plan 
failure"))
+                                
.when(mock).plan(Mockito.any(StatementBase.class), 
Mockito.any(TQueryOptions.class));
+                    })) {
+                Assert.assertThrows(RuntimeException.class, 
executor::executeInternalQuery);
+            }
+
+            
Mockito.verify(workloadRuntimeStatusMgr).submitFinishQueryToAudit(auditEventCaptor.capture());
+        } finally {
+            Deencapsulation.setField(env, "workloadRuntimeStatusMgr", 
originalWorkloadRuntimeStatusMgr);
+        }
+
+        AuditEvent auditEvent = auditEventCaptor.getValue();
+        Assert.assertEquals(AuditEvent.EventType.AFTER_QUERY, auditEvent.type);
+        Assert.assertEquals("ERR", auditEvent.state);
+        Assert.assertEquals(ErrorCode.ERR_INTERNAL_ERROR.getCode(), 
auditEvent.errorCode);
+        Assert.assertNotNull(auditEvent.errorMessage);
+        Assert.assertTrue("error message should mention root cause, got: " + 
auditEvent.errorMessage,
+                auditEvent.errorMessage.contains("mock plan failure"));
+        Assert.assertTrue("audit event should be marked as internal", 
auditEvent.isInternal);
+        Assert.assertTrue("audit event should be marked as query", 
auditEvent.isQuery);
+        Assert.assertTrue("audit event should be marked as nereids", 
auditEvent.isNereids);
+        Assert.assertEquals("select * from table1", auditEvent.stmt);
+        Assert.assertEquals("a8ec30e5ad0820f8c5bd16a82a4491ca", 
auditEvent.sqlHash);
+    }
 }
diff --git 
a/regression-test/suites/fault_injection_p0/test_audit_log_internal_query_failure.groovy
 
b/regression-test/suites/fault_injection_p0/test_audit_log_internal_query_failure.groovy
deleted file mode 100644
index 233187e582e..00000000000
--- 
a/regression-test/suites/fault_injection_p0/test_audit_log_internal_query_failure.groovy
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.
-
-// Regression test for CIR-20019: when an internal query fails (for example
-// the column-statistics gathering SQL that ANALYZE issues against a user
-// table), the audit log entry must record state=ERR with a descriptive
-// error_message instead of the previous misleading state=OK / return_rows=0.
-suite('test_audit_log_internal_query_failure', 'nonConcurrent') {
-    def tbl = 'test_audit_log_internal_query_failure_t1'
-
-    setGlobalVarTemporary([enable_audit_plugin: true], {
-        try {
-            sql "drop table if exists ${tbl}"
-            sql """
-                create table ${tbl} (k int, v int)
-                duplicate key(k)
-                distributed by hash(k) buckets 1
-                properties('replication_num'='1')
-            """
-            sql "insert into ${tbl} values(1,10),(2,20),(3,30)"
-
-            // Limit the injected IO error to this table's tablet so concurrent
-            // reads on system tables (such as audit_log itself) are 
unaffected.
-            def tabletRows = sql_return_maparray "show tablets from ${tbl}"
-            assertFalse(tabletRows.isEmpty(), "expected at least one tablet 
for ${tbl}")
-            def tabletId = tabletRows[0].TabletId
-
-            // Capture the FE-side current timestamp to filter audit rows so
-            // stale entries from previous runs do not satisfy the assertion.
-            def startTime = (sql_return_maparray "select now() as 
ts")[0].ts.toString()
-
-            GetDebugPoint().clearDebugPointsForAllBEs()
-            try {
-                GetDebugPoint().enableDebugPointForAllBEs(
-                        "LocalFileReader::read_at_impl.io_error",
-                        [ sub_path: "/${tabletId}/" ])
-
-                test {
-                    sql "analyze table ${tbl} with sync"
-                    exception "IO_ERROR"
-                }
-            } finally {
-                GetDebugPoint().clearDebugPointsForAllBEs()
-            }
-
-            def currentDb = (sql_return_maparray "select database() as 
db")[0].db.toString()
-            def fullTableName = "internal.${currentDb}.${tbl}"
-
-            // Force a flush so the failed internal query is queryable from
-            // __internal_schema.audit_log.
-            // The failed gather SQL reads from our user table and runs as an
-            // internal query; it must show up with state=ERR. Filter by start
-            // time to avoid matching stale entries from previous runs.
-            // Use queried_tables_and_views instead of stmt because stmt can be
-            // truncated by audit_plugin_max_sql_length when running in CI.
-            def query = """select state, error_code, error_message
-                           from __internal_schema.audit_log
-                           where is_internal = 1
-                             and array_contains(queried_tables_and_views, 
'${fullTableName}')
-                             and state = 'ERR'
-                             and `time` >= '${startTime}'
-                           order by `time` desc limit 1"""
-            def res = []
-            int retry = 60
-            while (res.isEmpty() && retry-- > 0) {
-                sql "call flush_audit_log()"
-                sleep(2000)
-                res = sql_return_maparray "${query}"
-            }
-            assertFalse(res.isEmpty(),
-                    "expected an audit_log entry with state=ERR for the failed 
gather query")
-            assertEquals('ERR', res[0].state.toString())
-            assertNotEquals('0', res[0].error_code.toString())
-            assertNotNull(res[0].error_message)
-            assertTrue(!res[0].error_message.toString().isEmpty(),
-                    "audit_log error_message should not be empty, got: 
${res[0].error_message}")
-        } finally {
-            try {
-                sql "drop table if exists ${tbl}"
-            } catch (Throwable ignored) {
-            }
-        }
-    })
-}


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

Reply via email to