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 724995b6a8c Refactor ProxySQLExecutorTest (#38059)
724995b6a8c is described below
commit 724995b6a8ca915e18f62254d76e9fde000d6311
Author: Liang Zhang <[email protected]>
AuthorDate: Mon Feb 16 15:03:40 2026 +0800
Refactor ProxySQLExecutorTest (#38059)
---
.codex/skills/gen-ut/SKILL.md | 91 +++++++++++++++++++++-
.../backend/connector/ProxySQLExecutorTest.java | 27 +++----
2 files changed, 100 insertions(+), 18 deletions(-)
diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index 8560f375dc4..444426968f3 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -52,7 +52,8 @@ Module resolution order:
- `R2`: test types and naming
- Non-parameterized scenarios `MUST` use JUnit `@Test`.
- - Data-driven scenarios `MUST` use JUnit `@ParameterizedTest`, and display
names `MUST` use `@ParameterizedTest(name = "{0}")`.
+ - Data-driven scenarios `MUST` use JUnit `@ParameterizedTest(name = "{0}")`
with `@MethodSource` + `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`.
- `MUST NOT` use `@RepeatedTest`.
- Test method naming `MUST` follow `CODE_OF_CONDUCT.md`: use the `assert`
prefix; when a single test uniquely covers a production method, use
`assert<MethodName>`.
@@ -73,6 +74,7 @@ Module resolution order:
- Each test method `MUST` cover only one scenario.
- Each test method `MUST` call the target public method at most once;
additional assertions are allowed in the same scenario.
- For parameterized tests, each `Arguments` row `MUST` represent one
independent scenario and one branch/path mapping unit for `R4`.
+ - For parameterized tests, `Arguments` row count `MUST` be greater than or
equal to 3.
- Tests `MUST` exercise behavior through public methods only.
- Public production methods with business logic `MUST` be covered with
dedicated test methods.
- Dedicated test targets `MUST` follow the `R4` branch-mapping exclusion
scope.
@@ -103,7 +105,7 @@ Module resolution order:
- "Declared assertion differences" means differences explicitly recorded in
the delivery report.
- High-fit candidates `MUST` be refactored directly to parameterized form.
- For high-fit candidates, a "do not recommend refactor" conclusion is
allowed only when refactoring causes significant readability/diagnosability
regression, and the exception `MUST` include a `Necessity reason tag` with
concrete evidence.
- - Parameter construction `SHOULD` prefer `Arguments + @MethodSource`; `MAY`
use clearer options such as `@CsvSource`/`@EnumSource`.
+ - Parameter construction `MUST` use `Arguments + @MethodSource`.
- `MUST` provide either a "recommend refactor" or "do not recommend
refactor" conclusion with reasons for each candidate; when no candidates exist,
`MUST` output "no candidates + decision reason".
- If high-fit candidates exist but neither parameterized refactor nor valid
`KEEP` evidence is present, status `MUST NOT` be concluded as `R10-A`.
@@ -158,6 +160,7 @@ Module resolution order:
- `R15-A` (parameterization enforcement): if an `R8` high-fit candidate
exists, the corresponding tests `MUST` be parameterized with
`@ParameterizedTest(name = "{0}")`, unless a valid `KEEP` exception is recorded.
- `R15-B` (metadata accessor test ban): unless the user explicitly requests
it in the current turn, tests targeting `getType` / `getOrder` / `getTypeClass`
`MUST NOT` be added.
- `R15-C` (scope mutation guard): test-generation tasks `MUST NOT` introduce
new diffs under any `src/main/` path.
+ - `R15-D` (parameterized argument floor): each `@ParameterizedTest` `MUST`
bind to `@MethodSource` providers that together contain at least 3 `Arguments`
rows; otherwise it is a violation.
## Workflow
@@ -359,6 +362,90 @@ PY
'
```
+5.2 `R15-D` parameterized minimum arguments 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)
+ARGUMENT_ROW_PATTERN = re.compile(r"\\b(?:Arguments\\.of|arguments)\\s*\\(")
+
+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, annotation_block):
+ resolved = []
+ matches = list(METHOD_SOURCE_PATTERN.finditer(annotation_block))
+ 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
+ raw = raw.strip()
+ normalized = re.sub(r"\\bvalue\\s*=\\s*", "", raw)
+ names = re.findall(r'"([^"]+)"', normalized)
+ for name in names:
+ # Ignore external references such as "pkg.Class#method"; they are
unresolved in this scan.
+ 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")
+ 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):
+ annotation_block = match.group(1)
+ method_name = match.group(2)
+ line = source.count("\\n", 0, match.start()) + 1
+ source_methods = parse_method_sources(method_name, annotation_block)
+ if not source_methods:
+ violations.append(f"{path}:{line} method={method_name} missing
@MethodSource")
+ continue
+ total_rows = 0
+ unresolved = []
+ for provider in source_methods:
+ body = method_bodies.get(provider)
+ if body is None:
+ unresolved.append(provider)
+ continue
+ total_rows += len(ARGUMENT_ROW_PATTERN.findall(body))
+ if unresolved:
+ violations.append(f"{path}:{line} method={method_name}
unresolvedProviders={','.join(unresolved)}")
+ continue
+ if total_rows < 3:
+ violations.append(f"{path}:{line} method={method_name}
argumentsRows={total_rows}")
+if violations:
+ print("[R15-D] each @ParameterizedTest must have >= 3 Arguments rows from
@MethodSource")
+ for each in violations:
+ print(each)
+ sys.exit(1)
+PY
+'
+```
+
6. `R14` hard-gate scan:
```bash
bash -lc '
diff --git
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
index 64004998879..7fbfa9a099a 100644
---
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
+++
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
@@ -72,6 +72,7 @@ import
org.apache.shardingsphere.transaction.api.TransactionType;
import org.apache.shardingsphere.transaction.rule.TransactionRule;
import org.apache.shardingsphere.transaction.spi.TransactionHook;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@@ -186,17 +187,16 @@ class ProxySQLExecutorTest {
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
}
- @ParameterizedTest(name = "{0}")
- @MethodSource("constructorScenarios")
- void assertConstructor(final String name, final String
currentDatabaseName, final String schemaName, final boolean hasSchemaName) {
-
when(connectionSession.getCurrentDatabaseName()).thenReturn(currentDatabaseName);
- assertNotNull(createProxySQLExecutor(schemaName,
hasSchemaName).getSqlFederationEngine());
+ @Test
+ void assertConstructorUseUsedDatabaseWhenCurrentDatabaseNameEmpty() {
+ when(connectionSession.getCurrentDatabaseName()).thenReturn("");
+ assertNotNull(createProxySQLExecutor("foo_schema",
true).getSqlFederationEngine());
}
- private Stream<Arguments> constructorScenarios() {
- return Stream.of(
-
Arguments.of("constructor-use-used-database-when-current-empty", "",
"foo_schema", true),
-
Arguments.of("constructor-use-default-schema-when-schema-missing", "foo_db",
"foo_schema", false));
+ @Test
+ void assertConstructorUseDefaultSchemaWhenSchemaMissing() {
+ when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db");
+ assertNotNull(createProxySQLExecutor("foo_schema",
false).getSqlFederationEngine());
}
@ParameterizedTest(name = "{0}")
@@ -346,9 +346,8 @@ class ProxySQLExecutorTest {
Arguments.of("driver-prepare-failed-throws-original", false,
createInsertStatement(postgresqlDatabaseType), false));
}
- @ParameterizedTest(name = "{0}")
- @MethodSource("xaMetaDataRefreshScenarios")
- void
assertCheckExecutePrerequisitesWithMetaDataRefreshInXATransaction(final String
name) {
+ @Test
+ void assertCheckExecutePrerequisitesWithMetaDataRefreshInXATransaction() {
DatabaseType databaseType = mock(DatabaseType.class);
DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class);
when(dialectDatabaseMetaData.getTransactionOption()).thenReturn(new
DialectTransactionOption(false, false, false, true, true,
@@ -362,10 +361,6 @@ class ProxySQLExecutorTest {
}
}
- private Stream<Arguments> xaMetaDataRefreshScenarios() {
- return
Stream.of(Arguments.of("ddl-create-xa-with-metadata-refresh-supported"));
- }
-
private ProxySQLExecutor createProxySQLExecutor(final String schemaName,
final boolean hasSchemaName) {
return new ProxySQLExecutor(JDBCDriverType.STATEMENT,
databaseConnectionManager, databaseProxyConnector,
createConstructorStatementContext(schemaName, hasSchemaName));
}