Skip to content

Commit 67cc636

Browse files
authored
Improve tests for various error scenarios (#1642)
* Improve tests for various error scenarios - Regex meta characters in index names should not break warning detection (required code fix) - Improve tests that only checked number of rows (need to validate data as well) - Test positive case allowing ignored duplicates on migration key - Test behavior with PanicOnWarnings disabled * Address Copilot feedback * Add test for warnings on composite unique keys * Add test for updating pk with duplicate * Improve replica test debugging - Print log excerpt on failure - Upload full log artifacts on failure * Reduce flakiness in update-pk test * Revise test * More robust test fix * Make MySQL wait strategy less flaky Removed the `wait.ForExposedPort()` override from test files. The tests will now use the MySQL module's default wait strategy (`wait.ForLog("port: 3306 MySQL Community Server")`), which properly waits for MySQL to be ready to accept connections. Otherwise the port may be exposed, but MySQL is still initializing and not ready to accept connections. * Customize update-pk integration test Add support for test-specific execution so that we can guarantee that we're specifically testing the DML apply phase * Fix regression in integration test harness * Add test timeouts and fix error propagation Prevent indefinite test hangs by adding 120-second timeout and duration reporting. Fix silent error drops by propagating errors from background write goroutines to PanicAbort channel. Check for abort in sleepWhileTrue loop and handle its error in cutOver.
1 parent b000b24 commit 67cc636

File tree

11 files changed

+656
-85
lines changed

11 files changed

+656
-85
lines changed

.github/workflows/replica-tests.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ jobs:
2828
- name: Run tests
2929
run: script/docker-gh-ost-replica-tests run
3030

31+
- name: Set artifact name
32+
if: failure()
33+
run: |
34+
ARTIFACT_NAME=$(echo "${{ matrix.image }}" | tr '/:' '-')
35+
echo "ARTIFACT_NAME=test-logs-${ARTIFACT_NAME}" >> $GITHUB_ENV
36+
37+
- name: Upload test logs on failure
38+
if: failure()
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: ${{ env.ARTIFACT_NAME }}
42+
path: /tmp/gh-ost-test.*
43+
retention-days: 7
44+
3145
- name: Teardown environment
3246
if: always()
3347
run: script/docker-gh-ost-replica-tests down

go/logic/applier.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ func NewApplier(migrationContext *base.MigrationContext) *Applier {
9494
}
9595
}
9696

97+
// compileMigrationKeyWarningRegex compiles a regex pattern that matches duplicate key warnings
98+
// for the migration's unique key. Duplicate warnings are formatted differently across MySQL versions,
99+
// hence the optional table name prefix. Metacharacters in table/index names are escaped to avoid
100+
// regex syntax errors.
101+
func (this *Applier) compileMigrationKeyWarningRegex() (*regexp.Regexp, error) {
102+
escapedTable := regexp.QuoteMeta(this.migrationContext.GetGhostTableName())
103+
escapedKey := regexp.QuoteMeta(this.migrationContext.UniqueKey.NameInGhostTable)
104+
migrationUniqueKeyPattern := fmt.Sprintf(`for key '(%s\.)?%s'`, escapedTable, escapedKey)
105+
migrationKeyRegex, err := regexp.Compile(migrationUniqueKeyPattern)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to compile migration key pattern: %w", err)
108+
}
109+
return migrationKeyRegex, nil
110+
}
111+
97112
func (this *Applier) InitDBConnections() (err error) {
98113
applierUri := this.connectionConfig.GetDBUri(this.migrationContext.DatabaseName)
99114
uriWithMulti := fmt.Sprintf("%s&multiStatements=true", applierUri)
@@ -928,6 +943,12 @@ func (this *Applier) ApplyIterationInsertQuery() (chunkSize int64, rowsAffected
928943
return nil, err
929944
}
930945

946+
// Compile regex once before loop to avoid performance penalty and handle errors properly
947+
migrationKeyRegex, err := this.compileMigrationKeyWarningRegex()
948+
if err != nil {
949+
return nil, err
950+
}
951+
931952
var sqlWarnings []string
932953
for rows.Next() {
933954
var level, message string
@@ -936,10 +957,7 @@ func (this *Applier) ApplyIterationInsertQuery() (chunkSize int64, rowsAffected
936957
this.migrationContext.Log.Warningf("Failed to read SHOW WARNINGS row")
937958
continue
938959
}
939-
// Duplicate warnings are formatted differently across mysql versions, hence the optional table name prefix
940-
migrationUniqueKeyExpression := fmt.Sprintf("for key '(%s\\.)?%s'", this.migrationContext.GetGhostTableName(), this.migrationContext.UniqueKey.NameInGhostTable)
941-
matched, _ := regexp.MatchString(migrationUniqueKeyExpression, message)
942-
if strings.Contains(message, "Duplicate entry") && matched {
960+
if strings.Contains(message, "Duplicate entry") && migrationKeyRegex.MatchString(message) {
943961
continue
944962
}
945963
sqlWarnings = append(sqlWarnings, fmt.Sprintf("%s: %s (%d)", level, message, code))
@@ -1570,6 +1588,12 @@ func (this *Applier) ApplyDMLEventQueries(dmlEvents [](*binlog.BinlogDMLEvent))
15701588
return rollback(err)
15711589
}
15721590

1591+
// Compile regex once before loop to avoid performance penalty and handle errors properly
1592+
migrationKeyRegex, err := this.compileMigrationKeyWarningRegex()
1593+
if err != nil {
1594+
return rollback(err)
1595+
}
1596+
15731597
var sqlWarnings []string
15741598
for rows.Next() {
15751599
var level, message string
@@ -1578,10 +1602,7 @@ func (this *Applier) ApplyDMLEventQueries(dmlEvents [](*binlog.BinlogDMLEvent))
15781602
this.migrationContext.Log.Warningf("Failed to read SHOW WARNINGS row")
15791603
continue
15801604
}
1581-
// Duplicate warnings are formatted differently across mysql versions, hence the optional table name prefix
1582-
migrationUniqueKeyExpression := fmt.Sprintf("for key '(%s\\.)?%s'", this.migrationContext.GetGhostTableName(), this.migrationContext.UniqueKey.NameInGhostTable)
1583-
matched, _ := regexp.MatchString(migrationUniqueKeyExpression, message)
1584-
if strings.Contains(message, "Duplicate entry") && matched {
1605+
if strings.Contains(message, "Duplicate entry") && migrationKeyRegex.MatchString(message) {
15851606
// Duplicate entry on migration unique key is expected during binlog replay
15861607
// (row was already copied during bulk copy phase)
15871608
continue

0 commit comments

Comments
 (0)