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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9f30b905173 Add more test cases on MySQLComQueryPacketTest and 
MySQLTextResultSetRowPacketTest (#38191)
9f30b905173 is described below

commit 9f30b90517320729c9bf69d4ee31a163c2f6a6dc
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Feb 25 14:51:19 2026 +0800

    Add more test cases on MySQLComQueryPacketTest and 
MySQLTextResultSetRowPacketTest (#38191)
    
    * Add more test cases on MySQLComQueryPacketTest and 
MySQLTextResultSetRowPacketTest
    
    * Add more test cases on MySQLComQueryPacketTest and 
MySQLTextResultSetRowPacketTest
    
    * Add more test cases on MySQLComQueryPacketTest and 
MySQLTextResultSetRowPacketTest
---
 .codex/skills/gen-ut/SKILL.md                      |  76 ++++++++++
 .../text/MySQLTextResultSetRowPacketTest.java      | 158 ++++++++++++++++-----
 .../query/text/query/MySQLComQueryPacketTest.java  |  48 +++++--
 3 files changed, 238 insertions(+), 44 deletions(-)

diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index c56bbee1075..8fbd7773108 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -54,6 +54,7 @@ Module resolution order:
   - Non-parameterized scenarios `MUST` use JUnit `@Test`.
   - Data-driven scenarios `MUST` use JUnit `@ParameterizedTest(name = "{0}")` 
with `@MethodSource` + `Arguments`.
   - Parameterized test method signatures `MUST` use `final String name` as the 
first parameter.
+  - Parameterized tests `MUST NOT` use `Consumer` (including 
`java.util.function.Consumer` and its generic forms) in method signatures or 
scenario-transport arguments.
   - Each parameterized test `MUST` provide at least 3 `Arguments` rows; fewer 
than 3 is a violation and `MUST` be converted to non-parameterized `@Test`.
   - Parameterized tests `MUST NOT` introduce new nested type declarations 
(member/local helper `class` / `interface` / `enum` / `record`) for scenario 
transport; use `Arguments` rows plus existing or JDK types instead.
   - `MUST NOT` use `@RepeatedTest`.
@@ -174,6 +175,7 @@ Module resolution order:
   - `R15-F` (parameterized switch ban): `@ParameterizedTest` method bodies 
`MUST NOT` contain `switch` statements.
   - `R15-G` (parameterized nested-type ban): when a file contains 
`@ParameterizedTest`, newly introduced diff lines `MUST NOT` add nested helper 
type declarations (`class` / `interface` / `enum` / `record`) inside the test 
class.
   - `R15-H` (boolean variable assertion style): for variable-driven boolean 
expectations, tests `MUST` assert with `assertThat(actual, is(expected))`, and 
`MUST NOT` use control-flow dispatch only to choose `assertTrue`/`assertFalse`.
+  - `R15-I` (parameterized Consumer ban): files containing 
`@ParameterizedTest` `MUST NOT` introduce or retain `Consumer`-based scenario 
transport in parameterized method signatures or `@MethodSource` argument rows.
 
 ## Workflow
 
@@ -566,6 +568,80 @@ PY
 '
 ```
 
+5.6 `R15-I` parameterized Consumer ban scan:
+```bash
+bash -lc '
+python3 - <ResolvedTestFileSet> <<'"'"'PY'"'"'
+import re
+import sys
+from pathlib import Path
+
+PARAM_METHOD_PATTERN = 
re.compile(r"@ParameterizedTest(?:\\s*\\([^)]*\\))?\\s*(?:@\\w+(?:\\s*\\([^)]*\\))?\\s*)*void\\s+(assert\\w+)\\s*\\(([^)]*)\\)",
 re.S)
+METHOD_SOURCE_PATTERN = re.compile(r"@MethodSource(?:\\s*\\(([^)]*)\\))?")
+METHOD_DECL_PATTERN = 
re.compile(r"(?:private|protected|public)?\\s*(?:static\\s+)?[\\w$<>\\[\\], 
?]+\\s+(\\w+)\\s*\\([^)]*\\)\\s*\\{", re.S)
+CONSUMER_TOKEN_PATTERN = re.compile(r"\\bConsumer\\s*(?:<|\\b)")
+
+def extract_block(text, brace_index):
+    depth = 0
+    index = brace_index
+    while index < len(text):
+        if "{" == text[index]:
+            depth += 1
+        elif "}" == text[index]:
+            depth -= 1
+            if 0 == depth:
+                return text[brace_index + 1:index]
+        index += 1
+    return ""
+
+def parse_method_sources(method_name, source, method_start):
+    header = source[max(0, method_start - 320):method_start]
+    matches = list(METHOD_SOURCE_PATTERN.finditer(header))
+    if not matches:
+        return []
+    resolved = []
+    for each in matches:
+        raw = each.group(1)
+        if raw is None or not raw.strip():
+            resolved.append(method_name)
+            continue
+        normalized = re.sub(r"\\bvalue\\s*=\\s*", "", raw.strip())
+        for name in re.findall(r'"([^"]+)"', normalized):
+            resolved.append(name.split("#", 1)[-1])
+    return resolved
+
+violations = []
+for path in (each for each in sys.argv[1:] if each.endswith(".java")):
+    source = Path(path).read_text(encoding="utf-8")
+    if "@ParameterizedTest" not in source:
+        continue
+    method_bodies = {}
+    for match in METHOD_DECL_PATTERN.finditer(source):
+        method_name = match.group(1)
+        brace_index = source.find("{", match.start())
+        if brace_index < 0:
+            continue
+        method_bodies[method_name] = extract_block(source, brace_index)
+    for match in PARAM_METHOD_PATTERN.finditer(source):
+        method_name = match.group(1)
+        params = match.group(2)
+        line = source.count("\\n", 0, match.start()) + 1
+        if CONSUMER_TOKEN_PATTERN.search(params):
+            violations.append(f"{path}:{line} method={method_name} 
reason=consumerInParameterizedMethodSignature")
+        provider_names = parse_method_sources(method_name, source, 
match.start())
+        for each_provider in provider_names:
+            body = method_bodies.get(each_provider)
+            if body and CONSUMER_TOKEN_PATTERN.search(body):
+                violations.append(f"{path}:{line} method={method_name} 
provider={each_provider} reason=consumerInMethodSourceArguments")
+if violations:
+    print("[R15-I] parameterized tests must not use Consumer in signatures or 
@MethodSource argument rows")
+    for each in violations:
+        print(each)
+    sys.exit(1)
+PY
+'
+```
+
 6. `R14` hard-gate scan:
 ```bash
 bash -lc '
diff --git 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/MySQLTextResultSetRowPacketTest.java
 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/MySQLTextResultSetRowPacketTest.java
index 16ad403cbfc..6a5d083aac3 100644
--- 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/MySQLTextResultSetRowPacketTest.java
+++ 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/MySQLTextResultSetRowPacketTest.java
@@ -18,18 +18,36 @@
 package 
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text;
 
 import 
org.apache.shardingsphere.database.protocol.mysql.payload.MySQLPacketPayload;
+import org.apache.shardingsphere.database.protocol.payload.PacketPayload;
+import org.apache.shardingsphere.infra.exception.generic.UnknownSQLException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.math.BigDecimal;
+import java.sql.Clob;
+import java.sql.SQLException;
 import java.sql.Timestamp;
 import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
+import java.time.LocalTime;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.stream.Stream;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -41,55 +59,123 @@ class MySQLTextResultSetRowPacketTest {
     private MySQLPacketPayload payload;
     
     @Test
-    void assertNew() {
+    void assertNewWithColumnCount() {
         when(payload.readStringLenenc()).thenReturn("value_a", null, 
"value_c");
-        new MySQLTextResultSetRowPacket(payload, 3);
+        assertThat(new MySQLTextResultSetRowPacket(payload, 3).getData(), 
is(Arrays.asList("value_a", null, "value_c")));
         verify(payload, times(3)).readStringLenenc();
     }
     
     @Test
-    void assertWrite() {
-        long now = System.currentTimeMillis();
-        Timestamp timestamp = new Timestamp(now);
-        MySQLTextResultSetRowPacket actual = new 
MySQLTextResultSetRowPacket(Arrays.asList(null, "value", BigDecimal.ONE, new 
byte[]{}, timestamp, Boolean.TRUE));
-        actual.write(payload);
-        verify(payload).writeInt1(0xfb);
-        verify(payload).writeStringLenenc("value");
-        verify(payload).writeStringLenenc("1");
-        if (0 == timestamp.getNanos()) {
-            
verify(payload).writeStringLenenc(timestamp.toString().split("\\.")[0]);
-        } else {
-            verify(payload).writeStringLenenc(timestamp.toString());
+    void assertNewWithZeroColumnCount() {
+        assertTrue(new MySQLTextResultSetRowPacket(payload, 
0).getData().isEmpty());
+        verify(payload, never()).readStringLenenc();
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("writeBasicValueArguments")
+    void assertWriteBasicValue(final String name, final Object value, final 
boolean writeNullMarker, final String expectedStringValue, final byte[] 
expectedBytesValue) {
+        new 
MySQLTextResultSetRowPacket(Collections.singletonList(value)).write((PacketPayload)
 payload);
+        if (writeNullMarker) {
+            verify(payload).writeInt1(0xfb);
+        }
+        if (null != expectedStringValue) {
+            verify(payload).writeStringLenenc(expectedStringValue);
         }
-        verify(payload).writeBytesLenenc(new byte[]{1});
+        if (null != expectedBytesValue) {
+            verify(payload).writeBytesLenenc(argThat(actual -> 
Arrays.equals(actual, expectedBytesValue)));
+        }
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("writeTimestampArguments")
+    void assertWriteTimestamp(final String name, final Timestamp value, final 
String expectedValue) {
+        new 
MySQLTextResultSetRowPacket(Collections.singletonList(value)).write((PacketPayload)
 payload);
+        verify(payload).writeStringLenenc(expectedValue);
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("writeLocalDateTimeArguments")
+    void assertWriteLocalDateTime(final String name, final LocalDateTime 
value, final String expectedValue) {
+        new 
MySQLTextResultSetRowPacket(Collections.singletonList(value)).write((PacketPayload)
 payload);
+        verify(payload).writeStringLenenc(expectedValue);
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("writeLocalTimeArguments")
+    void assertWriteLocalTime(final String name, final LocalTime value, final 
String expectedValue) {
+        new 
MySQLTextResultSetRowPacket(Collections.singletonList(value)).write((PacketPayload)
 payload);
+        verify(payload).writeStringLenenc(expectedValue);
     }
     
     @Test
-    void assertTimestampWithoutNanos() {
-        long now = System.currentTimeMillis() / 1000L * 1000L;
-        Timestamp timestamp = new Timestamp(now);
-        MySQLTextResultSetRowPacket actual = new 
MySQLTextResultSetRowPacket(Arrays.asList(null, "value", BigDecimal.ONE, new 
byte[]{}, timestamp));
-        actual.write(payload);
-        verify(payload).writeInt1(0xfb);
-        verify(payload).writeStringLenenc("value");
-        verify(payload).writeStringLenenc("1");
-        
verify(payload).writeStringLenenc(timestamp.toString().split("\\.")[0]);
+    void assertWriteClob() throws SQLException {
+        byte[] expectedBytes = new byte[]{10, 20};
+        Clob clob = mock(Clob.class);
+        when(clob.getAsciiStream()).thenReturn(new 
ByteArrayInputStream(expectedBytes));
+        new 
MySQLTextResultSetRowPacket(Collections.singletonList(clob)).write((PacketPayload)
 payload);
+        verify(payload).writeBytesLenenc(argThat(actual -> 
Arrays.equals(actual, expectedBytes)));
     }
     
     @Test
-    void assertLocalDateTime() {
-        String localDateTimeStr = "2021-08-23T17:30:30";
-        LocalDateTime dateTime = LocalDateTime.parse(localDateTimeStr, 
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
-        MySQLTextResultSetRowPacket actual = new 
MySQLTextResultSetRowPacket(Collections.singletonList(dateTime));
-        actual.write(payload);
-        
verify(payload).writeStringLenenc(DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss").format(LocalDateTime.parse(localDateTimeStr, 
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))));
+    void assertWriteClobWithIOException() throws SQLException {
+        IOException expectedCause = new IOException("read error");
+        InputStream inputStream = new InputStream() {
+            
+            @Override
+            public int read() throws IOException {
+                throw expectedCause;
+            }
+        };
+        Clob clob = mock(Clob.class);
+        when(clob.getAsciiStream()).thenReturn(inputStream);
+        MySQLTextResultSetRowPacket packet = new 
MySQLTextResultSetRowPacket(Collections.singletonList(clob));
+        UnknownSQLException actual = assertThrows(UnknownSQLException.class, 
() -> packet.write((PacketPayload) payload));
+        assertThat(actual.getCause(), is(expectedCause));
     }
     
     @Test
-    void assertLocalDateTimeWithMicros() {
-        LocalDateTime dateTime = LocalDateTime.of(2022, 2, 18, 17, 32, 38, 
123456000);
-        MySQLTextResultSetRowPacket actual = new 
MySQLTextResultSetRowPacket(Collections.singletonList(dateTime));
-        actual.write(payload);
-        verify(payload).writeStringLenenc("2022-02-18 17:32:38.123456");
+    void assertWriteClobWithSQLException() throws SQLException {
+        SQLException expectedCause = new SQLException("sql error");
+        Clob clob = mock(Clob.class);
+        when(clob.getAsciiStream()).thenThrow(expectedCause);
+        UnknownSQLException actual = assertThrows(UnknownSQLException.class, 
() -> new 
MySQLTextResultSetRowPacket(Collections.singletonList(clob)).write((PacketPayload)
 payload));
+        assertThat(actual.getCause(), is(expectedCause));
+    }
+    
+    private static Stream<Arguments> writeBasicValueArguments() {
+        byte[] binary = new byte[]{1, 2, 3};
+        return Stream.of(
+                Arguments.of("Null", null, true, null, null),
+                Arguments.of("ByteArray", binary, false, null, binary),
+                Arguments.of("BigDecimal", new BigDecimal("123.4500"), false, 
"123.4500", null),
+                Arguments.of("BooleanTrue", Boolean.TRUE, false, null, new 
byte[]{1}),
+                Arguments.of("BooleanFalse", Boolean.FALSE, false, null, new 
byte[]{0}),
+                Arguments.of("DefaultToString", "value_a", false, "value_a", 
null));
+    }
+    
+    private static Stream<Arguments> writeTimestampArguments() {
+        Timestamp noNanos = Timestamp.valueOf("2024-05-01 11:22:33");
+        Timestamp withMicros = Timestamp.valueOf("2024-05-01 11:22:33.123456");
+        Timestamp withNanos = Timestamp.valueOf("2024-05-01 
11:22:33.123456789");
+        return Stream.of(
+                Arguments.of("WithoutNanos", noNanos, 
noNanos.toString().split("\\.")[0]),
+                Arguments.of("WithMicros", withMicros, withMicros.toString()),
+                Arguments.of("WithNanos", withNanos, withNanos.toString()));
+    }
+    
+    private static Stream<Arguments> writeLocalDateTimeArguments() {
+        return Stream.of(
+                Arguments.of("WithoutNanos", LocalDateTime.of(2024, 5, 1, 11, 
22, 33), "2024-05-01 11:22:33"),
+                Arguments.of("NoTrailingMicrosecondZero", 
LocalDateTime.of(2024, 5, 1, 11, 22, 33, 123456000), "2024-05-01 
11:22:33.123456"),
+                Arguments.of("WithTrailingMicrosecondZero", 
LocalDateTime.of(2024, 5, 1, 11, 22, 33, 123450000), "2024-05-01 
11:22:33.12345"),
+                Arguments.of("AllMicrosecondsZero", LocalDateTime.of(2024, 5, 
1, 11, 22, 33, 1), "2024-05-01 11:22:33"));
+    }
+    
+    private static Stream<Arguments> writeLocalTimeArguments() {
+        return Stream.of(
+                Arguments.of("WithoutNanos", LocalTime.of(11, 22, 33), 
"11:22:33"),
+                Arguments.of("NoTrailingMicrosecondZero", LocalTime.of(11, 22, 
33, 123456000), "11:22:33.123456"),
+                Arguments.of("WithTrailingMicrosecondZero", LocalTime.of(11, 
22, 33, 123450000), "11:22:33.12345"),
+                Arguments.of("AllMicrosecondsZero", LocalTime.of(11, 22, 33, 
1), "11:22:33"));
     }
 }
diff --git 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
index 08067cf1df9..5fea6f5f085 100644
--- 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
+++ 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
@@ -21,11 +21,16 @@ import 
org.apache.shardingsphere.database.protocol.mysql.packet.command.MySQLCom
 import 
org.apache.shardingsphere.database.protocol.mysql.payload.MySQLPacketPayload;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
-import static org.hamcrest.Matchers.is;
+import java.util.stream.Stream;
+
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -35,19 +40,46 @@ class MySQLComQueryPacketTest {
     @Mock
     private MySQLPacketPayload payload;
     
-    @Test
-    void assertNew() {
-        when(payload.readStringEOF()).thenReturn("SELECT id FROM tbl");
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newWithStringArguments")
+    void assertNewWithString(final String name, final String inputSQL, final 
String expectedSQL, final boolean expectedWriteRouteOnly) {
+        MySQLComQueryPacket actual = new MySQLComQueryPacket(inputSQL);
+        assertThat(actual.getSQL(), is(expectedSQL));
+        assertThat(actual.getHintValueContext().isWriteRouteOnly(), 
is(expectedWriteRouteOnly));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newWithPayloadArguments")
+    void assertNewWithPayload(final String name, final String inputSQL, final 
String expectedSQL, final boolean expectedWriteRouteOnly) {
+        when(payload.readStringEOF()).thenReturn(inputSQL);
         MySQLComQueryPacket actual = new MySQLComQueryPacket(payload);
-        assertThat(actual.getSQL(), is("SELECT id FROM tbl"));
+        assertThat(actual.getSQL(), is(expectedSQL));
+        assertThat(actual.getHintValueContext().isWriteRouteOnly(), 
is(expectedWriteRouteOnly));
     }
     
     @Test
     void assertWrite() {
-        when(payload.readStringEOF()).thenReturn("SELECT id FROM tbl");
-        MySQLComQueryPacket actual = new MySQLComQueryPacket(payload);
-        actual.write(payload);
+        new MySQLComQueryPacket("SELECT id FROM tbl").write(payload);
         verify(payload).writeInt1(MySQLCommandPacketType.COM_QUERY.getValue());
         verify(payload).writeStringEOF("SELECT id FROM tbl");
     }
+    
+    @Test
+    void assertGetSQL() {
+        assertThat(new MySQLComQueryPacket("/* SHARDINGSPHERE_HINT: 
WRITE_ROUTE_ONLY=true */ SELECT id FROM tbl").getSQL(), is("SELECT id FROM 
tbl"));
+    }
+    
+    private static Stream<Arguments> newWithStringArguments() {
+        return Stream.of(
+                Arguments.of("WithoutHint", "SELECT id FROM tbl", "SELECT id 
FROM tbl", false),
+                Arguments.of("WithHintToken", "/* SHARDINGSPHERE_HINT: 
WRITE_ROUTE_ONLY=true */ SELECT id FROM tbl", "SELECT id FROM tbl", true),
+                Arguments.of("WithHintAlias", "/* ShardingSphere hint: 
WRITE_ROUTE_ONLY=true */ SELECT id FROM tbl", "SELECT id FROM tbl", true));
+    }
+    
+    private static Stream<Arguments> newWithPayloadArguments() {
+        return Stream.of(
+                Arguments.of("WithoutHint", "SELECT id FROM tbl", "SELECT id 
FROM tbl", false),
+                Arguments.of("WithHintToken", "/* SHARDINGSPHERE_HINT: 
WRITE_ROUTE_ONLY=true */ SELECT id FROM tbl", "SELECT id FROM tbl", true),
+                Arguments.of("WithHintAlias", "/* ShardingSphere hint: 
WRITE_ROUTE_ONLY=true */ SELECT id FROM tbl", "SELECT id FROM tbl", true));
+    }
 }

Reply via email to