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));
+ }
}