Skip to content

Comments

test: Add a prerequisite assumption verification test for correctness of sharedSchema#4252

Open
hongyunyan wants to merge 4 commits intopingcap:masterfrom
hongyunyan:0224-schema
Open

test: Add a prerequisite assumption verification test for correctness of sharedSchema#4252
hongyunyan wants to merge 4 commits intopingcap:masterfrom
hongyunyan:0224-schema

Conversation

@hongyunyan
Copy link
Collaborator

@hongyunyan hongyunyan commented Feb 24, 2026

What problem does this PR solve?

Issue Number: close #4125

What is changed and how it works?

This pull request enhances the project's robustness by adding a critical test that safeguards against breaking changes in upstream TiDB's schema definitions. By proactively verifying the structure of key data types, it ensures the continued compatibility and correctness of TiCDC's shared-schema functionality, thereby reducing the risk of unexpected failures during TiDB version upgrades.

Highlights

  • New Shared Schema Guard Test: Introduced a new test, TestLatestTiDBTableInfoSharedSchemaGuard, to verify the correctness of shared schema assumptions by checking the field contracts of critical TiDB structs (TableInfo, ColumnInfo, IndexInfo, IndexColumn, FieldType) against the latest TiDB master branch.
  • Upstream Compatibility Verification: The test dynamically fetches the latest TiDB module information and uses Go's AST parsing to extract struct fields, ensuring that any upstream changes in TiDB's schema-related structs are detected early, preventing potential compatibility issues for TiCDC's shared-schema logic.
  • Robust Field Comparison: Implemented utility functions (queryModuleInfo, decodeModuleInfo, extractStructFields, embeddedFieldName, diffFieldLists) to programmatically resolve module versions, parse Go source files, and compare expected struct fields with actual fields, providing detailed error messages upon mismatch.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Questions

Will it cause performance regression or break compatibility?
Do you need to update user documentation, design documentation or monitoring documentation?

Release note

Please refer to [Release Notes Language Style Guide](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/release-notes-style-guide.html) to write a quality release note.

If you don't think this PR needs a release note then fill it with `None`.

Summary by CodeRabbit

  • Tests
    • Added a validation test that verifies upstream struct definitions relied on by shared-schema logic. It compares expected vs actual fields from upstream sources, detects mismatches early, and fails with clear diagnostics to help maintain compatibility and stability of schema-related integrations.

@ti-chi-bot ti-chi-bot bot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Feb 24, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @hongyunyan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the project's robustness by adding a critical test that safeguards against breaking changes in upstream TiDB's schema definitions. By proactively verifying the structure of key data types, it ensures the continued compatibility and correctness of TiCDC's shared-schema functionality, thereby reducing the risk of unexpected failures during TiDB version upgrades.

Highlights

  • New Shared Schema Guard Test: Introduced a new test, TestLatestTiDBTableInfoSharedSchemaGuard, to verify the correctness of shared schema assumptions by checking the field contracts of critical TiDB structs (TableInfo, ColumnInfo, IndexInfo, IndexColumn, FieldType) against the latest TiDB master branch.
  • Upstream Compatibility Verification: The test dynamically fetches the latest TiDB module information and uses Go's AST parsing to extract struct fields, ensuring that any upstream changes in TiDB's schema-related structs are detected early, preventing potential compatibility issues for TiCDC's shared-schema logic.
  • Robust Field Comparison: Implemented utility functions (queryModuleInfo, decodeModuleInfo, extractStructFields, embeddedFieldName, diffFieldLists) to programmatically resolve module versions, parse Go source files, and compare expected struct fields with actual fields, providing detailed error messages upon mismatch.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • pkg/common/table_info_shared_schema_guard_test.go
    • Added a new test file to verify the field contracts of TiDB structs used by TiCDC's shared-schema logic.
    • Implemented functions to query Go module information, parse Go source code using AST, and extract struct field names.
    • Included a mechanism to compare expected struct fields with those found in the latest TiDB master branch, failing if discrepancies are found.
Activity
  • No review activity or comments have been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ti-chi-bot ti-chi-bot bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Feb 24, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Adds a new Go test that verifies upstream TiDB struct field contracts used by TiCDC's shared-schema logic by resolving module metadata, locating package sources, parsing Go AST (including embedded fields), and comparing extracted struct fields against expected lists.

Changes

Cohort / File(s) Summary
Shared Schema Guard Test
pkg/common/table_info_shared_schema_guard_test.go
New test that queries module info (via go list -m -json and go mod download), locates package dir or module zip, parses non-test Go files with the Go AST (including synthetic names for embedded fields), extracts fields for TableInfo, ColumnInfo, IndexInfo, IndexColumn, and FieldType, compares actual vs expected field lists, and fails with diagnostics (module path, version, hash, diffs) on mismatch.

Sequence Diagram(s)

sequenceDiagram
  participant Test as Test (pkg/common)
  participant GoTool as Go Tooling
  participant Upstream as Upstream Module (tidb)
  participant Parser as AST Parser
  participant Comparator as Field Comparator

  Test->>GoTool: query module metadata (`go list -m -json`)
  GoTool->>Upstream: download/locate module source if needed
  Upstream-->>GoTool: provide source dir or zip
  GoTool-->>Test: resolved package dir/zip
  Test->>Parser: parse non-test .go files, collect struct fields (handle embedded)
  Parser-->>Test: extracted struct field lists
  Test->>Comparator: compare extracted vs expected fields
  Comparator-->>Test: match / mismatch
  alt mismatch
    Test-->>Test: FailNow with diagnostics (module, version, hash, diffs)
  else match
    Test-->>Test: pass
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

lgtm, approved, size/L, release-note-none

Suggested reviewers

  • lidezhu
  • wk989898

Poem

🐰 I hopped through modules, sniffed each field with care,
Parsed AST burrows, found embedded whiskers there.
I named the hidden parts and lined them up in rows,
Contracts checked, shared-schema steady grows.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: Add a prerequisite assumption verification test for correctness of sharedSchema' clearly summarizes the main change—adding a test for shared-schema assumptions.
Description check ✅ Passed The description provides a complete picture with issue reference, implementation details, highlights, test checkboxes, and release notes section, meeting the template requirements.
Linked Issues check ✅ Passed The PR implements the core objective from issue #4125 by adding TestLatestTiDBTableInfoSharedSchemaGuard to verify struct field contracts and detect upstream TiDB changes.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the prerequisite assumption verification test; no unrelated modifications are present in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces a new test file, table_info_shared_schema_guard_test.go, which is a valuable addition for verifying the contract of upstream TiDB structs. This guard test helps ensure compatibility and detect breaking changes early, improving the robustness of the shared-schema logic. The implementation is thorough, correctly handling module information retrieval, AST parsing for struct fields, and providing clear error messages upon contract mismatches. This is a well-designed and important test.

@@ -0,0 +1,344 @@
// Copyright 2026 PingCAP, Inc.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The copyright year is set to 2026. This appears to be a typo and should likely be the current year or the year the file was created.

Suggested change
// Copyright 2026 PingCAP, Inc.
// Copyright 2024 PingCAP, Inc.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
pkg/common/table_info_shared_schema_guard_test.go (3)

231-246: ParseDir filter accepts all files, then manually skips _test.go — consider filtering upfront.

The ParseDir filter on line 233 returns true for every file, meaning test files are parsed into the AST and then skipped at line 244. Moving the test-file exclusion into the filter avoids unnecessary parsing work:

♻️ Suggested filter change
-	pkgs, err := parser.ParseDir(fileSet, packageDir, func(_ fs.FileInfo) bool {
-		return true
-	}, parser.SkipObjectResolution)
+	pkgs, err := parser.ParseDir(fileSet, packageDir, func(info fs.FileInfo) bool {
+		return !strings.HasSuffix(info.Name(), "_test.go")
+	}, parser.SkipObjectResolution)

Then the manual check at lines 244–246 can be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 231 - 246, In
extractStructFields update the parser.ParseDir file filter to return false for
files with names ending in "_test.go" (e.g., check the fs.FileInfo.Name()) so
test files are excluded during parsing, and then remove the subsequent manual
skip that checks strings.HasSuffix(fileName, "_test.go") inside the
package/files loop; this keeps the logic in the ParseDir filter and avoids
parsing test files unnecessarily.

189-202: decodeModuleInfo JSON extraction is reasonable but has a subtle edge-case.

bytes.IndexByte(output, '{') finds the first {, which works well for typical go list/go mod download output. However, json.Unmarshal(output[start:]) operates on everything from that first { to the end of the byte slice. If the command ever emits trailing output after the JSON (e.g., warnings on stderr mixed via CombinedOutput), the unmarshal could fail.

Using json.NewDecoder on a reader from output[start:] and calling Decode once would parse exactly one JSON object and ignore trailing bytes more robustly:

💡 Optional: more robust JSON decoding
 func decodeModuleInfo(output []byte) (moduleInfo, error) {
 	start := bytes.IndexByte(output, '{')
 	if start == -1 {
 		return moduleInfo{}, fmt.Errorf("no JSON object found in output: %s", string(output))
 	}
 
 	var mod moduleInfo
-	if err := json.Unmarshal(output[start:], &mod); err != nil {
+	if err := json.NewDecoder(bytes.NewReader(output[start:])).Decode(&mod); err != nil {
 		return moduleInfo{}, err
 	}
 	return mod, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 189 - 202,
The current decodeModuleInfo function finds the first '{' then calls
json.Unmarshal on output[start:], which fails if there are trailing non-JSON
bytes; change it to create an io.Reader from output[start:] (e.g.,
bytes.NewReader(output[start:])) and use json.NewDecoder(reader).Decode(&mod) to
decode exactly one JSON object and ignore any trailing bytes, returning mod and
any decode error; keep the function name decodeModuleInfo and variable mod the
same so callers remain unchanged.

133-187: queryModuleInfo correctly resolves and downloads module metadata.

The fallback from go list to go mod download when Dir is empty is a reasonable approach. The structured logging is compliant with project guidelines.

One consideration: this function shells out to go list -m -json <module>@master, which requires network access. If CI runs with restricted network or GOFLAGS=-mod=vendor, this test will fail. Consider gating behind a build tag (e.g., //go:build integration) or an environment-variable check so it doesn't break offline/hermetic test runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 133 - 187,
The test helper queryModuleInfo shells out to networked go commands and should
be gated to avoid failing in offline/hermetic CI; modify the file so
queryModuleInfo early-exits (call t.Skipf) when a guard is not set (either a
build tag like //go:build integration at the top of the file or an environment
check such as os.Getenv("RUN_INTEGRATION_TESTS") != "1"), or add the build tag
to the file header and document usage; reference the queryModuleInfo function
and use t.Skipf/t.Skip to skip the network-dependent behavior when the guard is
absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 231-246: In extractStructFields update the parser.ParseDir file
filter to return false for files with names ending in "_test.go" (e.g., check
the fs.FileInfo.Name()) so test files are excluded during parsing, and then
remove the subsequent manual skip that checks strings.HasSuffix(fileName,
"_test.go") inside the package/files loop; this keeps the logic in the ParseDir
filter and avoids parsing test files unnecessarily.
- Around line 189-202: The current decodeModuleInfo function finds the first '{'
then calls json.Unmarshal on output[start:], which fails if there are trailing
non-JSON bytes; change it to create an io.Reader from output[start:] (e.g.,
bytes.NewReader(output[start:])) and use json.NewDecoder(reader).Decode(&mod) to
decode exactly one JSON object and ignore any trailing bytes, returning mod and
any decode error; keep the function name decodeModuleInfo and variable mod the
same so callers remain unchanged.
- Around line 133-187: The test helper queryModuleInfo shells out to networked
go commands and should be gated to avoid failing in offline/hermetic CI; modify
the file so queryModuleInfo early-exits (call t.Skipf) when a guard is not set
(either a build tag like //go:build integration at the top of the file or an
environment check such as os.Getenv("RUN_INTEGRATION_TESTS") != "1"), or add the
build tag to the file header and document usage; reference the queryModuleInfo
function and use t.Skipf/t.Skip to skip the network-dependent behavior when the
guard is absent.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e03137 and d84eef1.

📒 Files selected for processing (1)
  • pkg/common/table_info_shared_schema_guard_test.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 57-139: The guard test TestLatestTiDBTableInfoSharedSchemaGuard
performs network-dependent go list/mod downloads against `@master` and must be
gated to keep default test runs deterministic; update the test to skip unless
explicitly enabled by (a) honoring testing.Short() (call t.Skip or t.Skipf when
testing.Short() is true) and (b) allowing an opt-in env var like
ENABLE_UPSTREAM_GUARD (check os.Getenv("ENABLE_UPSTREAM_GUARD") == "1" before
running) so the heavy logic that calls queryModuleInfo and
extractStructFieldsFromModule only runs when enabled; place the skip logic at
the start of TestLatestTiDBTableInfoSharedSchemaGuard and keep the rest of the
test (moduleCache, queryModuleInfo, assertStructContract) unchanged.
- Around line 203-214: The json.Unmarshal error returned from decodeModuleInfo
is unwrapped — change the return to wrap the library error using the project's
error helper (e.g., return moduleInfo{}, errors.Trace(err) or
errors.WrapError(err, "decodeModuleInfo") ) so the stack/context is preserved;
apply the same pattern to the other raw returns of json.Unmarshal and similar
library errors in this file (the other spots referenced in the review) so every
external error is wrapped at its origin before being returned.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d84eef1 and 63c9fbb.

📒 Files selected for processing (1)
  • pkg/common/table_info_shared_schema_guard_test.go

Comment on lines 57 to 139
// TestLatestTiDBTableInfoSharedSchemaGuard verifies the upstream struct fields
// that TiCDC shared-schema logic depends on.
func TestLatestTiDBTableInfoSharedSchemaGuard(t *testing.T) {
// This guard intentionally checks the latest TiDB master to detect
// upstream struct-field changes before TiCDC upgrades its pinned TiDB version.
cases := []structGuardCase{
{
name: "table_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "TableInfo",
expectedFields: []string{
"ID", "Name", "Charset", "Collate", "Columns", "Indices", "Constraints", "ForeignKeys", "State",
"PKIsHandle", "IsCommonHandle", "CommonHandleVersion",
"Comment", "AutoIncID", "AutoIncIDExtra", "AutoIDCache", "AutoRandID",
"MaxColumnID", "MaxIndexID", "MaxForeignKeyID", "MaxConstraintID", "UpdateTS", "AutoIDSchemaID",
"ShardRowIDBits", "MaxShardRowIDBits", "AutoRandomBits", "AutoRandomRangeBits", "PreSplitRegions",
"Partition", "Compression", "View", "Sequence", "Lock", "Version", "TiFlashReplica", "IsColumnar",
"TempTableType", "TableCacheStatusType", "PlacementPolicyRef", "StatsOptions",
"ExchangePartitionInfo", "TTLInfo", "IsActiveActive", "SoftdeleteInfo", "Affinity",
"Revision", "DBID", "Mode",
},
},
{
name: "column_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "ColumnInfo",
expectedFields: []string{
"ID", "Name", "Offset", "OriginDefaultValue", "OriginDefaultValueBit", "DefaultValue", "DefaultValueBit",
"DefaultIsExpr", "GeneratedExprString", "GeneratedStored", "Dependences", "FieldType", "ChangingFieldType",
"State", "Comment", "Hidden", "ChangeStateInfo", "Version",
},
},
{
name: "index_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexInfo",
expectedFields: []string{
"ID", "Name", "Table", "Columns", "State", "BackfillState", "Comment", "Tp", "Unique", "Primary",
"Invisible", "Global", "MVIndex", "VectorInfo", "InvertedInfo", "FullTextInfo",
"ConditionExprString", "AffectColumn", "GlobalIndexVersion",
},
},
{
name: "index_column_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexColumn",
expectedFields: []string{"Name", "Offset", "Length", "UseChangingType"},
},
{
name: "field_type_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb/pkg/parser",
pkgRelativePath: "types",
typeName: "FieldType",
expectedFields: []string{"tp", "flag", "flen", "decimal", "charset", "collate", "elems", "elemsIsBinaryLit", "array"},
},
}

moduleCache := make(map[string]*moduleInfo)
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
mod, ok := moduleCache[tc.modulePath]
if !ok {
mod = queryModuleInfo(t, tc.modulePath)
moduleCache[tc.modulePath] = mod
}

actualFields, err := extractStructFieldsFromModule(mod, tc.pkgRelativePath, tc.typeName)
require.NoError(
t,
err,
"extract struct fields failed for %s in module=%s version=%s",
tc.typeName,
mod.Path,
mod.Version,
)

assertStructContract(t, tc, mod, actualFields)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Gate the guard test to keep default runs deterministic.
This test shells out to go list/go mod download against @master, so it depends on network access and mutable upstream state. That makes normal unit test runs flaky. Consider skipping unless explicitly enabled (env var/build tag), and honor -short.

Suggested guard to keep default test runs deterministic
@@
 	"io"
 	"io/fs"
+	"os"
 	"os/exec"
@@
 func TestLatestTiDBTableInfoSharedSchemaGuard(t *testing.T) {
+	if testing.Short() || os.Getenv("TICDC_SHARED_SCHEMA_GUARD") == "" {
+		t.Skip("shared schema guard requires network; set TICDC_SHARED_SCHEMA_GUARD=1 to run")
+	}
 	// This guard intentionally checks the latest TiDB master to detect

As per coding guidelines **/*_test.go: favor deterministic tests and use testify/require.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TestLatestTiDBTableInfoSharedSchemaGuard verifies the upstream struct fields
// that TiCDC shared-schema logic depends on.
func TestLatestTiDBTableInfoSharedSchemaGuard(t *testing.T) {
// This guard intentionally checks the latest TiDB master to detect
// upstream struct-field changes before TiCDC upgrades its pinned TiDB version.
cases := []structGuardCase{
{
name: "table_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "TableInfo",
expectedFields: []string{
"ID", "Name", "Charset", "Collate", "Columns", "Indices", "Constraints", "ForeignKeys", "State",
"PKIsHandle", "IsCommonHandle", "CommonHandleVersion",
"Comment", "AutoIncID", "AutoIncIDExtra", "AutoIDCache", "AutoRandID",
"MaxColumnID", "MaxIndexID", "MaxForeignKeyID", "MaxConstraintID", "UpdateTS", "AutoIDSchemaID",
"ShardRowIDBits", "MaxShardRowIDBits", "AutoRandomBits", "AutoRandomRangeBits", "PreSplitRegions",
"Partition", "Compression", "View", "Sequence", "Lock", "Version", "TiFlashReplica", "IsColumnar",
"TempTableType", "TableCacheStatusType", "PlacementPolicyRef", "StatsOptions",
"ExchangePartitionInfo", "TTLInfo", "IsActiveActive", "SoftdeleteInfo", "Affinity",
"Revision", "DBID", "Mode",
},
},
{
name: "column_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "ColumnInfo",
expectedFields: []string{
"ID", "Name", "Offset", "OriginDefaultValue", "OriginDefaultValueBit", "DefaultValue", "DefaultValueBit",
"DefaultIsExpr", "GeneratedExprString", "GeneratedStored", "Dependences", "FieldType", "ChangingFieldType",
"State", "Comment", "Hidden", "ChangeStateInfo", "Version",
},
},
{
name: "index_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexInfo",
expectedFields: []string{
"ID", "Name", "Table", "Columns", "State", "BackfillState", "Comment", "Tp", "Unique", "Primary",
"Invisible", "Global", "MVIndex", "VectorInfo", "InvertedInfo", "FullTextInfo",
"ConditionExprString", "AffectColumn", "GlobalIndexVersion",
},
},
{
name: "index_column_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexColumn",
expectedFields: []string{"Name", "Offset", "Length", "UseChangingType"},
},
{
name: "field_type_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb/pkg/parser",
pkgRelativePath: "types",
typeName: "FieldType",
expectedFields: []string{"tp", "flag", "flen", "decimal", "charset", "collate", "elems", "elemsIsBinaryLit", "array"},
},
}
moduleCache := make(map[string]*moduleInfo)
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
mod, ok := moduleCache[tc.modulePath]
if !ok {
mod = queryModuleInfo(t, tc.modulePath)
moduleCache[tc.modulePath] = mod
}
actualFields, err := extractStructFieldsFromModule(mod, tc.pkgRelativePath, tc.typeName)
require.NoError(
t,
err,
"extract struct fields failed for %s in module=%s version=%s",
tc.typeName,
mod.Path,
mod.Version,
)
assertStructContract(t, tc, mod, actualFields)
})
// TestLatestTiDBTableInfoSharedSchemaGuard verifies the upstream struct fields
// that TiCDC shared-schema logic depends on.
func TestLatestTiDBTableInfoSharedSchemaGuard(t *testing.T) {
if testing.Short() || os.Getenv("TICDC_SHARED_SCHEMA_GUARD") == "" {
t.Skip("shared schema guard requires network; set TICDC_SHARED_SCHEMA_GUARD=1 to run")
}
// This guard intentionally checks the latest TiDB master to detect
// upstream struct-field changes before TiCDC upgrades its pinned TiDB version.
cases := []structGuardCase{
{
name: "table_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "TableInfo",
expectedFields: []string{
"ID", "Name", "Charset", "Collate", "Columns", "Indices", "Constraints", "ForeignKeys", "State",
"PKIsHandle", "IsCommonHandle", "CommonHandleVersion",
"Comment", "AutoIncID", "AutoIncIDExtra", "AutoIDCache", "AutoRandID",
"MaxColumnID", "MaxIndexID", "MaxForeignKeyID", "MaxConstraintID", "UpdateTS", "AutoIDSchemaID",
"ShardRowIDBits", "MaxShardRowIDBits", "AutoRandomBits", "AutoRandomRangeBits", "PreSplitRegions",
"Partition", "Compression", "View", "Sequence", "Lock", "Version", "TiFlashReplica", "IsColumnar",
"TempTableType", "TableCacheStatusType", "PlacementPolicyRef", "StatsOptions",
"ExchangePartitionInfo", "TTLInfo", "IsActiveActive", "SoftdeleteInfo", "Affinity",
"Revision", "DBID", "Mode",
},
},
{
name: "column_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "ColumnInfo",
expectedFields: []string{
"ID", "Name", "Offset", "OriginDefaultValue", "OriginDefaultValueBit", "DefaultValue", "DefaultValueBit",
"DefaultIsExpr", "GeneratedExprString", "GeneratedStored", "Dependences", "FieldType", "ChangingFieldType",
"State", "Comment", "Hidden", "ChangeStateInfo", "Version",
},
},
{
name: "index_info_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexInfo",
expectedFields: []string{
"ID", "Name", "Table", "Columns", "State", "BackfillState", "Comment", "Tp", "Unique", "Primary",
"Invisible", "Global", "MVIndex", "VectorInfo", "InvertedInfo", "FullTextInfo",
"ConditionExprString", "AffectColumn", "GlobalIndexVersion",
},
},
{
name: "index_column_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb",
pkgRelativePath: "pkg/meta/model",
typeName: "IndexColumn",
expectedFields: []string{"Name", "Offset", "Length", "UseChangingType"},
},
{
name: "field_type_fields_for_shared_schema",
modulePath: "github.com/pingcap/tidb/pkg/parser",
pkgRelativePath: "types",
typeName: "FieldType",
expectedFields: []string{"tp", "flag", "flen", "decimal", "charset", "collate", "elems", "elemsIsBinaryLit", "array"},
},
}
moduleCache := make(map[string]*moduleInfo)
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
mod, ok := moduleCache[tc.modulePath]
if !ok {
mod = queryModuleInfo(t, tc.modulePath)
moduleCache[tc.modulePath] = mod
}
actualFields, err := extractStructFieldsFromModule(mod, tc.pkgRelativePath, tc.typeName)
require.NoError(
t,
err,
"extract struct fields failed for %s in module=%s version=%s",
tc.typeName,
mod.Path,
mod.Version,
)
assertStructContract(t, tc, mod, actualFields)
})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 57 - 139, The
guard test TestLatestTiDBTableInfoSharedSchemaGuard performs network-dependent
go list/mod downloads against `@master` and must be gated to keep default test
runs deterministic; update the test to skip unless explicitly enabled by (a)
honoring testing.Short() (call t.Skip or t.Skipf when testing.Short() is true)
and (b) allowing an opt-in env var like ENABLE_UPSTREAM_GUARD (check
os.Getenv("ENABLE_UPSTREAM_GUARD") == "1" before running) so the heavy logic
that calls queryModuleInfo and extractStructFieldsFromModule only runs when
enabled; place the skip logic at the start of
TestLatestTiDBTableInfoSharedSchemaGuard and keep the rest of the test
(moduleCache, queryModuleInfo, assertStructContract) unchanged.

Comment on lines +203 to +214
// decodeModuleInfo parses module metadata JSON from go command output.
// Some go commands may print informational lines before JSON payload.
func decodeModuleInfo(output []byte) (moduleInfo, error) {
start := bytes.IndexByte(output, '{')
if start == -1 {
return moduleInfo{}, fmt.Errorf("no JSON object found in output: %s", string(output))
}

var mod moduleInfo
if err := json.Unmarshal(output[start:], &mod); err != nil {
return moduleInfo{}, err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the specific lines
find . -name "table_info_shared_schema_guard_test.go" -type f

Repository: pingcap/ticdc

Length of output: 108


🏁 Script executed:

# Once found, let's read the file around lines 203-214
# First get the total line count
wc -l ./pkg/common/table_info_shared_schema_guard_test.go

Repository: pingcap/ticdc

Length of output: 112


🏁 Script executed:

# Read lines 200-220 to see the context around lines 203-214
sed -n '200,220p' ./pkg/common/table_info_shared_schema_guard_test.go

Repository: pingcap/ticdc

Length of output: 827


🏁 Script executed:

# Check the imports at the top of the file to see if errors is imported
head -50 ./pkg/common/table_info_shared_schema_guard_test.go

Repository: pingcap/ticdc

Length of output: 1206


🏁 Script executed:

# Search for other unwrapped error returns in this file
grep -n "return.*err" ./pkg/common/table_info_shared_schema_guard_test.go

Repository: pingcap/ticdc

Length of output: 224


Wrap library errors at the origin.
decodeModuleInfo returns raw json.Unmarshal errors without wrapping, which loses stack context required by the repo's error-handling guidelines. Wrap with errors.Trace() or errors.WrapError() before returning.

Example fix for decodeModuleInfo
 	"github.com/pingcap/log"
+	"github.com/pingcap/errors"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/zap"

 	if err := json.Unmarshal(output[start:], &mod); err != nil {
-		return moduleInfo{}, err
+		return moduleInfo{}, errors.Trace(err)
 	}

Note: Similar unwrapped errors appear on lines 260, 293, 306, 309, 333, and 344—apply the same wrapping pattern throughout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 203 - 214,
The json.Unmarshal error returned from decodeModuleInfo is unwrapped — change
the return to wrap the library error using the project's error helper (e.g.,
return moduleInfo{}, errors.Trace(err) or errors.WrapError(err,
"decodeModuleInfo") ) so the stack/context is preserved; apply the same pattern
to the other raw returns of json.Unmarshal and similar library errors in this
file (the other spots referenced in the review) so every external error is
wrapped at its origin before being returned.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
pkg/common/table_info_shared_schema_guard_test.go (2)

203-213: [Unresolved] Third-party errors returned unwrapped throughout the file.

json.Unmarshal (line 213), zip.OpenReader (line 260), file.Open() (line 294), io.ReadAll (line 299), rc.Close() (line 302), and parser.ParseFile (line 307) all return nil, err without errors.Trace(err), losing stack context required by the repo's error-handling convention. The past review identified all these locations — none have been addressed.

As per coding guidelines **/*.go: wrap third-party library errors immediately with errors.Trace(err).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 203 - 213,
Multiple third-party errors in this file are returned directly instead of being
wrapped with errors.Trace, losing stack context; update all return sites in
functions like decodeModuleInfo (json.Unmarshal), and the functions that call
zip.OpenReader, file.Open, io.ReadAll, rc.Close, and parser.ParseFile to wrap
the underlying error with errors.Trace(err) before returning (e.g., return nil,
errors.Trace(err)) so they follow the repo convention and preserve stack traces.

59-141: [Unresolved] Gate the test behind an opt-in mechanism to keep default runs deterministic.

The test still shells out to go list @master`` / go mod download on every `go test ./...` invocation, making it network-dependent and non-deterministic. The past review requested a `testing.Short()` guard and an environment-variable opt-in, neither of which has been applied.

As per coding guidelines **/*_test.go: favor deterministic tests and use testify/require.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 59 - 141,
Gate TestLatestTiDBTableInfoSharedSchemaGuard so it doesn't run network calls by
default: at the top of the function check testing.Short() and an opt-in env var
(e.g. os.Getenv("RUN_LATEST_TIDB_GUARD") == "1") and call t.Skip when not
enabled, before any calls to queryModuleInfo or extractStructFieldsFromModule;
ensure os and testing are imported and use testify/require for assertions as
already used.
🧹 Nitpick comments (2)
pkg/common/table_info_shared_schema_guard_test.go (2)

330-343: _test.go files are parsed unnecessarily — filter them in the ParseDir callback.

The filter function always returns true, so ParseDir parses every file including _test.go, which the loop then discards. Move the exclusion into the filter to avoid the redundant AST work.

♻️ Proposed refactor
-	pkgs, err := parser.ParseDir(fileSet, packageDir, func(_ fs.FileInfo) bool {
-		return true
-	}, parser.SkipObjectResolution)
+	pkgs, err := parser.ParseDir(fileSet, packageDir, func(fi fs.FileInfo) bool {
+		return !strings.HasSuffix(fi.Name(), "_test.go")
+	}, parser.SkipObjectResolution)
 	if err != nil {
 		return nil, err
 	}

 	fieldSet := make(map[string]struct{})
 	found := false
 	for _, pkg := range pkgs {
 		for fileName, fileNode := range pkg.Files {
-			if strings.HasSuffix(fileName, "_test.go") {
-				continue
-			}
 			if err := collectStructFieldsFromFile(fileNode, typeName, fieldSet, &found); err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 330 - 343,
The ParseDir call currently passes a callback that always returns true causing
parser.ParseDir to include _test.go files only to have them filtered later;
update the anonymous filter callback passed to parser.ParseDir (the func(_
fs.FileInfo) bool {...}) so it returns false for files with names ending in
"_test.go" (e.g. use fs.FileInfo.Name() and strings.HasSuffix) and true
otherwise, keeping the existing parser.SkipObjectResolution option; this moves
the exclusion into the ParseDir scan and avoids building ASTs for test files
referenced by fileSet/packageDir.

279-287: Substring match for zip entry path could falsely include files from sibling directories.

strings.Index(name, "/"+targetDir) will match any occurrence of the suffix in the path, e.g. a hypothetical internal/pkg/meta/model/foo.go would also match /pkg/meta/model/. Module zip entries are guaranteed to start with <module>@<version>/, so it is safer to strip that fixed prefix before comparing the relative path.

♻️ More precise path matching
-		targetPrefix := "/" + targetDir
-		pkgPos := strings.Index(name, targetPrefix)
-		if pkgPos < 0 {
-			continue
-		}
-		rest := name[pkgPos+len(targetPrefix):]
+		// Module zip entries are "<module>@<version>/rel/path". Strip the root prefix.
+		atIdx := strings.Index(name, "@")
+		if atIdx < 0 {
+			continue
+		}
+		slashIdx := strings.Index(name[atIdx:], "/")
+		if slashIdx < 0 {
+			continue
+		}
+		relPath := name[atIdx+slashIdx+1:] // "rel/path/file.go"
+		if !strings.HasPrefix(relPath, targetDir) {
+			continue
+		}
+		rest := relPath[len(targetDir):]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 279 - 287,
The current substring check using strings.Index(name, "/"+targetDir) can match
occurrences inside deeper paths; instead strip the leading module@version prefix
from the zip entry (use the first '/' in name) to get the true relative path,
then compare that relative path against targetDir; update the logic that
computes pkgPos/rest/targetPrefix (variables name, targetPrefix, pkgPos, rest,
targetDir) so you derive rel := name[firstSlash+1:] and then check rel for the
exact sibling directory (e.g., ensure rel has no extra '/' after the targetDir)
rather than searching the whole name for "/"+targetDir.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 148-164: The test uses exec.Command for external calls (the "go
list" invocation that constructs cmd := exec.Command(...) and the "go mod
download" invocation that constructs downloadCmd := exec.Command(...)) with no
timeout; change both to use exec.CommandContext with a context that has a
bounded timeout (use context.WithTimeout to create ctx and defer cancel) so the
test cannot hang indefinitely, and pass ctx into exec.CommandContext for the "go
list" and "go mod download" commands (keep existing output/error handling and
decodeModuleInfo usage unchanged).

---

Duplicate comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 203-213: Multiple third-party errors in this file are returned
directly instead of being wrapped with errors.Trace, losing stack context;
update all return sites in functions like decodeModuleInfo (json.Unmarshal), and
the functions that call zip.OpenReader, file.Open, io.ReadAll, rc.Close, and
parser.ParseFile to wrap the underlying error with errors.Trace(err) before
returning (e.g., return nil, errors.Trace(err)) so they follow the repo
convention and preserve stack traces.
- Around line 59-141: Gate TestLatestTiDBTableInfoSharedSchemaGuard so it
doesn't run network calls by default: at the top of the function check
testing.Short() and an opt-in env var (e.g. os.Getenv("RUN_LATEST_TIDB_GUARD")
== "1") and call t.Skip when not enabled, before any calls to queryModuleInfo or
extractStructFieldsFromModule; ensure os and testing are imported and use
testify/require for assertions as already used.

---

Nitpick comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 330-343: The ParseDir call currently passes a callback that always
returns true causing parser.ParseDir to include _test.go files only to have them
filtered later; update the anonymous filter callback passed to parser.ParseDir
(the func(_ fs.FileInfo) bool {...}) so it returns false for files with names
ending in "_test.go" (e.g. use fs.FileInfo.Name() and strings.HasSuffix) and
true otherwise, keeping the existing parser.SkipObjectResolution option; this
moves the exclusion into the ParseDir scan and avoids building ASTs for test
files referenced by fileSet/packageDir.
- Around line 279-287: The current substring check using strings.Index(name,
"/"+targetDir) can match occurrences inside deeper paths; instead strip the
leading module@version prefix from the zip entry (use the first '/' in name) to
get the true relative path, then compare that relative path against targetDir;
update the logic that computes pkgPos/rest/targetPrefix (variables name,
targetPrefix, pkgPos, rest, targetDir) so you derive rel := name[firstSlash+1:]
and then check rel for the exact sibling directory (e.g., ensure rel has no
extra '/' after the targetDir) rather than searching the whole name for
"/"+targetDir.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63c9fbb and 9d4c075.

📒 Files selected for processing (1)
  • pkg/common/table_info_shared_schema_guard_test.go

Comment on lines 148 to 164
cmd := exec.Command("go", "list", "-m", "-json", modulePath+"@master")
output, err := cmd.CombinedOutput()
require.NoError(t, err, "query module %s failed: %s", modulePath, string(output))

mod, err := decodeModuleInfo(output)
require.NoError(t, err, "unmarshal module metadata for %s failed", modulePath)

// go list may omit Dir when the queried version is not yet downloaded.
// In that case, explicitly download the resolved version to get a stable
// source directory for AST parsing.
if mod.Dir == "" {
query := modulePath + "@master"
if mod.Version != "" {
query = modulePath + "@" + mod.Version
}
downloadCmd := exec.Command("go", "mod", "download", "-json", query)
downloadOutput, downloadErr := downloadCmd.CombinedOutput()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

exec.Command calls have no timeout — CI jobs can hang indefinitely.

Both go list (line 148) and go mod download (line 163) have no context deadline. A slow or unreachable module proxy will block the test runner forever. Use exec.CommandContext with a bounded timeout.

⏱️ Proposed fix using exec.CommandContext
+import (
+	"context"
+	"time"
+	// … existing imports …
+)

 func queryModuleInfo(t *testing.T, modulePath string) *moduleInfo {
 	t.Helper()

-	cmd := exec.Command("go", "list", "-m", "-json", modulePath+"@master")
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath+"@master")
 	output, err := cmd.CombinedOutput()
 	require.NoError(t, err, "query module %s failed: %s", modulePath, string(output))

Apply the same ctx / exec.CommandContext wrapping to the go mod download call on line 163.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 148 - 164,
The test uses exec.Command for external calls (the "go list" invocation that
constructs cmd := exec.Command(...) and the "go mod download" invocation that
constructs downloadCmd := exec.Command(...)) with no timeout; change both to use
exec.CommandContext with a context that has a bounded timeout (use
context.WithTimeout to create ctx and defer cancel) so the test cannot hang
indefinitely, and pass ctx into exec.CommandContext for the "go list" and "go
mod download" commands (keep existing output/error handling and decodeModuleInfo
usage unchanged).

@ti-chi-bot ti-chi-bot bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Feb 24, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
pkg/common/table_info_shared_schema_guard_test.go (3)

67-68: ⚠️ Potential issue | 🟠 Major

Gate this test behind an opt-in mechanism to preserve CI determinism.

This test shells out to go mod download … @master``, which requires network access and depends on mutable upstream state. Running it unconditionally in every go test invocation makes unit test suites non-deterministic and flaky. Add a skip guard at the top of the function.

Proposed skip guard
 func TestLatestTiDBTableInfoSharedSchemaGuard(t *testing.T) {
+	if testing.Short() || os.Getenv("TICDC_SHARED_SCHEMA_GUARD") == "" {
+		t.Skip("shared-schema guard requires network; set TICDC_SHARED_SCHEMA_GUARD=1 to run")
+	}
 	// This guard intentionally checks the latest TiDB master to detect

As per coding guidelines **/*_test.go: favor deterministic tests and use testify/require.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 67 - 68, The
test TestLatestTiDBTableInfoSharedSchemaGuard performs networked,
non-deterministic work and must be opt-in: at the top of the function add a skip
guard that only runs when an explicit opt-in flag is set (for example check an
env var like RUN_NETWORK_TESTS=="1" or a build tag) and call t.Skip with a clear
message otherwise; also ensure the test imports and uses testify/require where
applicable per project guidelines.

210-216: ⚠️ Potential issue | 🟠 Major

exec.Command has no timeout — CI jobs can hang indefinitely.

go mod download against @master through a potentially unreachable proxy has no deadline. Use exec.CommandContext with a bounded timeout so the test fails cleanly instead of blocking the runner.

Proposed fix
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	defer cancel()
-	cmd := exec.Command("go", "mod", "download", "-json", modulePath+"@master")
+	cmd := exec.CommandContext(ctx, "go", "mod", "download", "-json", modulePath+"@master")

Requires adding "context" and "time" to imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 210 - 216,
The test uses exec.Command("go", "mod", "download", ...) with no timeout which
can hang CI; change to use context.WithTimeout and exec.CommandContext so the
subprocess is bounded. Replace the exec.Command call with a context.WithTimeout
(e.g. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second);
defer cancel()) and create the command with exec.CommandContext(ctx, "go",
"mod", "download", "-json", modulePath+"@master"), then call
cmd.CombinedOutput() as before; also add the "context" and "time" imports.
Ensure you keep existing error handling around decodeModuleInfo and the
require.NoError checks.

234-236: Unwrapped library error from json.Unmarshal.

Per the repo's error-handling guidelines, third-party/library errors should be wrapped with errors.Trace(err) to preserve stack context. The same applies to other raw return nil, err sites in this file (lines 293, 309, 322, 354, 359, 362, 387).

Example fix
 	if err := json.Unmarshal(output[start:], &mod); err != nil {
-		return moduleInfo{}, err
+		return moduleInfo{}, errors.Trace(err)
 	}

Add "github.com/pingcap/errors" to imports and apply the same pattern to all other raw library error returns.

Based on learnings: "When an error comes from a third-party or library call in Go, wrap it immediately with errors.Trace(err) or errors.WrapError(...) to attach a stack trace."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 234 - 236,
Wrap raw library errors returned from calls like json.Unmarshal(output[start:],
&mod) with errors.Trace(err) and add "github.com/pingcap/errors" to the imports;
specifically replace the raw return of moduleInfo{}, err with moduleInfo{},
errors.Trace(err) for the json.Unmarshal call (and apply the same pattern for
other raw returns that return nil, err in this file). Update all other
occurrences mentioned (the other raw library returns in this file) to use
errors.Trace(err) so third-party errors preserve stack context; keep existing
return types (e.g., moduleInfo{}, nil) unchanged except for wrapping the error
value.
🧹 Nitpick comments (3)
pkg/common/table_info_shared_schema_guard_test.go (3)

384-388: Build-tagged source files are parsed unconditionally.

parser.ParseFile does not evaluate //go:build constraints. If an upstream package ever defines the same struct in platform-specific files (e.g. foo_linux.go / foo_windows.go), the "type found multiple times" guard on line 444 would trigger a spurious failure. For TiDB model types this is currently fine, but consider filtering files with go/build.Context.MatchFile or at least documenting this assumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 384 - 388,
The test parses all files in sourceFiles with parser.ParseFile (using fileSet
and parser.SkipObjectResolution) without applying build-tag constraints, which
can cause duplicate-type errors when upstream packages have platform-specific
files; update the loop that calls parser.ParseFile to first filter sourceFiles
by evaluating build tags (e.g. use go/build.Context.MatchFile or equivalent) and
only parse files that match the current build context (or add a clear comment
documenting the assumption), so that parser.ParseFile is invoked only for files
valid for the active platform.

338-347: Zip entry path matching uses substring search — may false-match nested directories.

strings.Index(name, targetPrefix) finds the first occurrence of "/types/" (or whatever pkgRelativePath resolves to) anywhere in the zip entry path. If the module ever contains multiple directories ending with the same suffix (e.g. internal/types/ alongside types/), this could match the wrong package.

A safer approach is to strip the known <module>@<version>/ prefix first and then compare the remaining directory exactly:

Suggested approach
 	for _, file := range reader.File {
 		...
 		name := filepath.ToSlash(file.Name)
-		targetPrefix := "/" + targetDir
-		pkgPos := strings.Index(name, targetPrefix)
-		if pkgPos < 0 {
+		// Strip the first path component "<module>@<version>/"
+		slashIdx := strings.Index(name, "/")
+		if slashIdx < 0 {
 			continue
 		}
-		rest := name[pkgPos+len(targetPrefix):]
+		relPath := name[slashIdx+1:]
+		if !strings.HasPrefix(relPath, targetDir) {
+			continue
+		}
+		rest := relPath[len(targetDir):]

This is low-risk for the known TiDB modules today, but the exact-prefix approach is more robust if the upstream module structure evolves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 338 - 347,
The code uses strings.Index(name, targetPrefix) (where targetPrefix := "/" +
targetDir) which can match the suffix anywhere in the zip entry path; instead
strip the leading module@version component first and then do an exact segment
match: find the first '/' in name, set rest to the substring after that (replace
current pkgPos/rest logic), then check if rest equals targetDir or starts with
targetDir+"/" (using rest == targetDir || strings.HasPrefix(rest,
targetDir+"/")) so you only match the top-level package directory; update
references to name, targetDir, targetPrefix, pkgPos and rest accordingly in this
block.

129-131: Remove the unnecessary tc := tc re-binding; loop variables are per-iteration scoped in Go 1.22+.

The project targets Go 1.25.5, which makes the tc := tc pattern a no-op. This re-binding was a workaround for shared loop variable scoping in older Go versions but is unnecessary now and can be safely removed for cleaner code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/common/table_info_shared_schema_guard_test.go` around lines 129 - 131,
Remove the redundant loop-variable re-binding inside the test loop: in the
for-range over cases, delete the unnecessary "tc := tc" statement so each
iteration uses the per-iteration scoped loop variable "tc" directly when calling
t.Run and within the subtest; update the test body that references tc (the
subtest closure passed to t.Run) to rely on the loop's per-iteration scoping and
ensure no other re-bindings remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 67-68: The test TestLatestTiDBTableInfoSharedSchemaGuard performs
networked, non-deterministic work and must be opt-in: at the top of the function
add a skip guard that only runs when an explicit opt-in flag is set (for example
check an env var like RUN_NETWORK_TESTS=="1" or a build tag) and call t.Skip
with a clear message otherwise; also ensure the test imports and uses
testify/require where applicable per project guidelines.
- Around line 210-216: The test uses exec.Command("go", "mod", "download", ...)
with no timeout which can hang CI; change to use context.WithTimeout and
exec.CommandContext so the subprocess is bounded. Replace the exec.Command call
with a context.WithTimeout (e.g. ctx, cancel :=
context.WithTimeout(context.Background(), 30*time.Second); defer cancel()) and
create the command with exec.CommandContext(ctx, "go", "mod", "download",
"-json", modulePath+"@master"), then call cmd.CombinedOutput() as before; also
add the "context" and "time" imports. Ensure you keep existing error handling
around decodeModuleInfo and the require.NoError checks.
- Around line 234-236: Wrap raw library errors returned from calls like
json.Unmarshal(output[start:], &mod) with errors.Trace(err) and add
"github.com/pingcap/errors" to the imports; specifically replace the raw return
of moduleInfo{}, err with moduleInfo{}, errors.Trace(err) for the json.Unmarshal
call (and apply the same pattern for other raw returns that return nil, err in
this file). Update all other occurrences mentioned (the other raw library
returns in this file) to use errors.Trace(err) so third-party errors preserve
stack context; keep existing return types (e.g., moduleInfo{}, nil) unchanged
except for wrapping the error value.

---

Nitpick comments:
In `@pkg/common/table_info_shared_schema_guard_test.go`:
- Around line 384-388: The test parses all files in sourceFiles with
parser.ParseFile (using fileSet and parser.SkipObjectResolution) without
applying build-tag constraints, which can cause duplicate-type errors when
upstream packages have platform-specific files; update the loop that calls
parser.ParseFile to first filter sourceFiles by evaluating build tags (e.g. use
go/build.Context.MatchFile or equivalent) and only parse files that match the
current build context (or add a clear comment documenting the assumption), so
that parser.ParseFile is invoked only for files valid for the active platform.
- Around line 338-347: The code uses strings.Index(name, targetPrefix) (where
targetPrefix := "/" + targetDir) which can match the suffix anywhere in the zip
entry path; instead strip the leading module@version component first and then do
an exact segment match: find the first '/' in name, set rest to the substring
after that (replace current pkgPos/rest logic), then check if rest equals
targetDir or starts with targetDir+"/" (using rest == targetDir ||
strings.HasPrefix(rest, targetDir+"/")) so you only match the top-level package
directory; update references to name, targetDir, targetPrefix, pkgPos and rest
accordingly in this block.
- Around line 129-131: Remove the redundant loop-variable re-binding inside the
test loop: in the for-range over cases, delete the unnecessary "tc := tc"
statement so each iteration uses the per-iteration scoped loop variable "tc"
directly when calling t.Run and within the subtest; update the test body that
references tc (the subtest closure passed to t.Run) to rely on the loop's
per-iteration scoping and ensure no other re-bindings remain.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4c075 and bb386f2.

📒 Files selected for processing (1)
  • pkg/common/table_info_shared_schema_guard_test.go

@ti-chi-bot ti-chi-bot bot added needs-1-more-lgtm Indicates a PR needs 1 more LGTM. approved labels Feb 24, 2026
@ti-chi-bot ti-chi-bot bot added the lgtm label Feb 25, 2026
@ti-chi-bot
Copy link

ti-chi-bot bot commented Feb 25, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: lidezhu, wk989898

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot bot removed the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Feb 25, 2026
@ti-chi-bot
Copy link

ti-chi-bot bot commented Feb 25, 2026

[LGTM Timeline notifier]

Timeline:

  • 2026-02-24 13:20:18.656951926 +0000 UTC m=+188291.171746525: ☑️ agreed by lidezhu.
  • 2026-02-25 02:48:05.232164254 +0000 UTC m=+236757.746958863: ☑️ agreed by wk989898.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a prerequisite assumption verification test to tidb for sharedSchema

3 participants