This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git
The following commit(s) were added to refs/heads/master by this push:
new 1de80ee8 test: improve test coverage for pkg/util/errors (#962)
1de80ee8 is described below
commit 1de80ee838666298a5a6e93eda8b6612b804b436
Author: EVERFID <[email protected]>
AuthorDate: Thu Nov 6 10:29:15 2025 +0800
test: improve test coverage for pkg/util/errors (#962)
---
pkg/util/errors/error_test.go | 504 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 504 insertions(+)
diff --git a/pkg/util/errors/error_test.go b/pkg/util/errors/error_test.go
new file mode 100644
index 00000000..acd77142
--- /dev/null
+++ b/pkg/util/errors/error_test.go
@@ -0,0 +1,504 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package errors
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// TestSeataError_Error tests the Error() method of SeataError
+func TestSeataError_Error(t *testing.T) {
+ tests := []struct {
+ name string
+ code TransactionErrorCode
+ message string
+ parent error
+ expected string
+ }{
+ {
+ name: "error with parent error",
+ code: TransactionErrorCodeBeginFailed,
+ message: "transaction failed",
+ parent: errors.New("database connection lost"),
+ expected: "SeataError code 1, msg transaction failed,
parent msg is database connection lost",
+ },
+ {
+ name: "error without parent error",
+ code: TransactionErrorCodeLockKeyConflict,
+ message: "timeout occurred",
+ parent: nil,
+ expected: "SeataError code 2, msg timeout occurred,
parent msg is %!s(<nil>)",
+ },
+ {
+ name: "error with empty message",
+ code: TransactionErrorCodeUnknown,
+ message: "",
+ parent: errors.New("some error"),
+ expected: "SeataError code 0, msg , parent msg is some
error",
+ },
+ {
+ name: "error with zero code and no parent",
+ code: TransactionErrorCodeUnknown,
+ message: "empty error",
+ parent: nil,
+ expected: "SeataError code 0, msg empty error, parent
msg is %!s(<nil>)",
+ },
+ {
+ name: "error with formatted parent error",
+ code: TransactionErrorCode(100),
+ message: "rollback failed",
+ parent: fmt.Errorf("nested error: %w",
errors.New("root cause")),
+ expected: "SeataError code 100, msg rollback failed,
parent msg is nested error: root cause",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ seataErr := SeataError{
+ Code: tt.code,
+ Message: tt.message,
+ Parent: tt.parent,
+ }
+
+ got := seataErr.Error()
+ assert.Equal(t, tt.expected, got)
+ })
+ }
+}
+
+// TestNew tests the New() constructor function
+func TestNew(t *testing.T) {
+ tests := []struct {
+ name string
+ code TransactionErrorCode
+ msg string
+ parent error
+ }{
+ {
+ name: "create error with all fields",
+ code: TransactionErrorCodeBeginFailed,
+ msg: "test message",
+ parent: errors.New("parent error"),
+ },
+ {
+ name: "create error without parent",
+ code: TransactionErrorCodeLockKeyConflict,
+ msg: "test message 2",
+ parent: nil,
+ },
+ {
+ name: "create error with empty message",
+ code: TransactionErrorCodeIO,
+ msg: "",
+ parent: errors.New("parent"),
+ },
+ {
+ name: "create error with zero code",
+ code: TransactionErrorCodeUnknown,
+ msg: "zero code message",
+ parent: nil,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := New(tt.code, tt.msg, tt.parent)
+
+ assert.NotNil(t, got)
+ assert.Equal(t, tt.code, got.Code)
+ assert.Equal(t, tt.msg, got.Message)
+ assert.Equal(t, tt.parent, got.Parent)
+ })
+ }
+}
+
+// TestNew_ReturnsPointer tests that New() returns a pointer
+func TestNew_ReturnsPointer(t *testing.T) {
+ err1 := New(TransactionErrorCodeBeginFailed, "message1", nil)
+ err2 := New(TransactionErrorCodeBeginFailed, "message1", nil)
+
+ // Two separate calls should return different pointers (compare
addresses)
+ if err1 == err2 {
+ t.Error("New() should return different pointers for different
calls")
+ }
+}
+
+// TestSeataError_AsError tests that SeataError implements error interface
+func TestSeataError_AsError(t *testing.T) {
+ var _ error = SeataError{}
+ var _ error = &SeataError{}
+
+ seataErr := New(TransactionErrorCodeBeginFailed, "test", nil)
+ var err error = seataErr
+
+ assert.NotNil(t, err, "SeataError should implement error interface")
+ assert.NotEmpty(t, err.Error(), "SeataError.Error() should return
non-empty string")
+}
+
+// TestSeataError_ErrorWithNestedSeataError tests nested SeataError
+func TestSeataError_ErrorWithNestedSeataError(t *testing.T) {
+ parentErr := New(TransactionErrorCodeBeginFailed, "parent error", nil)
+ childErr := New(TransactionErrorCodeLockKeyConflict, "child error",
parentErr)
+
+ expectedMsg := "SeataError code 2, msg child error, parent msg is
SeataError code 1, msg parent error, parent msg is %!s(<nil>)"
+ assert.Equal(t, expectedMsg, childErr.Error())
+}
+
+// TestSeataError_Fields tests direct field access
+func TestSeataError_Fields(t *testing.T) {
+ code := TransactionErrorCodeBranchRegisterFailed
+ msg := "test message"
+ parent := errors.New("parent error")
+
+ seataErr := SeataError{
+ Code: code,
+ Message: msg,
+ Parent: parent,
+ }
+
+ assert.Equal(t, code, seataErr.Code)
+ assert.Equal(t, msg, seataErr.Message)
+ assert.Equal(t, parent, seataErr.Parent)
+}
+
+// TestSeataError_LargeCode tests with large error code values
+func TestSeataError_LargeCode(t *testing.T) {
+ tests := []struct {
+ name string
+ code TransactionErrorCode
+ }{
+ {"max int32 value", TransactionErrorCode(2147483647)},
+ {"negative value", TransactionErrorCode(-1)},
+ {"large negative", TransactionErrorCode(-999999)},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := New(tt.code, "test", nil)
+ assert.Equal(t, tt.code, err.Code)
+
+ // Should not panic
+ assert.NotPanics(t, func() {
+ _ = err.Error()
+ })
+ })
+ }
+}
+
+// TestSeataError_LongMessage tests with long error messages
+func TestSeataError_LongMessage(t *testing.T) {
+ longMsg := string(make([]byte, 10000))
+ err := New(TransactionErrorCodeBeginFailed, longMsg, nil)
+
+ assert.Equal(t, longMsg, err.Message, "Long message not preserved
correctly")
+
+ // Should not panic with long message
+ assert.NotPanics(t, func() {
+ _ = err.Error()
+ })
+}
+
+// TestSeataError_SpecialCharacters tests messages with special characters
+func TestSeataError_SpecialCharacters(t *testing.T) {
+ specialMsgs := []string{
+ "message with\nnewline",
+ "message with\ttab",
+ "message with 中文字符",
+ "message with emoji 🚀",
+ "message with \"quotes\"",
+ "message with 'single quotes'",
+ }
+
+ for _, msg := range specialMsgs {
+ t.Run(msg, func(t *testing.T) {
+ err := New(TransactionErrorCodeBeginFailed, msg, nil)
+ assert.Equal(t, msg, err.Message, "Special character
message not preserved")
+
+ // Should not panic
+ assert.NotPanics(t, func() {
+ _ = err.Error()
+ })
+ })
+ }
+}
+
+// TestSeataError_WithRealErrorCodes tests with actual error codes from the
package
+func TestSeataError_WithRealErrorCodes(t *testing.T) {
+ tests := []struct {
+ name string
+ code TransactionErrorCode
+ }{
+ {"Unknown", TransactionErrorCodeUnknown},
+ {"BeginFailed", TransactionErrorCodeBeginFailed},
+ {"LockKeyConflict", TransactionErrorCodeLockKeyConflict},
+ {"IO", TransactionErrorCodeIO},
+ {"BranchRollbackFailedRetriable",
TransactionErrorCodeBranchRollbackFailedRetriable},
+ {"BranchRollbackFailedUnretriable",
TransactionErrorCodeBranchRollbackFailedUnretriable},
+ {"BranchRegisterFailed",
TransactionErrorCodeBranchRegisterFailed},
+ {"BranchReportFailed", TransactionErrorCodeBranchReportFailed},
+ {"LockableCheckFailed",
TransactionErrorCodeLockableCheckFailed},
+ {"BranchTransactionNotExist",
TransactionErrorCodeBranchTransactionNotExist},
+ {"GlobalTransactionNotExist",
TransactionErrorCodeGlobalTransactionNotExist},
+ {"GlobalTransactionNotActive",
TransactionErrorCodeGlobalTransactionNotActive},
+ {"GlobalTransactionStatusInvalid",
TransactionErrorCodeGlobalTransactionStatusInvalid},
+ {"FailedToSendBranchCommitRequest",
TransactionErrorCodeFailedToSendBranchCommitRequest},
+ {"FailedToSendBranchRollbackRequest",
TransactionErrorCodeFailedToSendBranchRollbackRequest},
+ {"FailedToAddBranch", TransactionErrorCodeFailedToAddBranch},
+ {"FailedLockGlobalTranscation",
TransactionErrorCodeFailedLockGlobalTranscation},
+ {"FailedWriteSession", TransactionErrorCodeFailedWriteSession},
+ {"FailedStore", FailedStore},
+ {"LockKeyConflictFailFast", LockKeyConflictFailFast},
+ {"TccFenceDbDuplicateKeyError", TccFenceDbDuplicateKeyError},
+ {"RollbackFenceError", RollbackFenceError},
+ {"CommitFenceError", CommitFenceError},
+ {"TccFenceDbError", TccFenceDbError},
+ {"PrepareFenceError", PrepareFenceError},
+ {"FenceBusinessError", FenceBusinessError},
+ {"FencePhaseError", FencePhaseError},
+ {"SQLUndoDirtyError", SQLUndoDirtyError},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := New(tt.code, "test message for "+tt.name, nil)
+ assert.NotNil(t, err)
+ assert.Equal(t, tt.code, err.Code)
+ assert.Contains(t, err.Error(), "test message for
"+tt.name)
+ })
+ }
+}
+
+// TestSeataError_NilParentFormatting tests the specific formatting of nil
parent
+func TestSeataError_NilParentFormatting(t *testing.T) {
+ err := New(TransactionErrorCodeBeginFailed, "test", nil)
+ errMsg := err.Error()
+
+ // Verify that nil parent is formatted as %!s(<nil>) due to the %s
format verb
+ assert.Contains(t, errMsg, "parent msg is %!s(<nil>)")
+}
+
+// TestSeataError_ErrorChaining tests error chaining with multiple levels
+func TestSeataError_ErrorChaining(t *testing.T) {
+ rootErr := errors.New("root cause")
+ level1Err := New(TransactionErrorCodeIO, "level 1", rootErr)
+ level2Err := New(TransactionErrorCodeBeginFailed, "level 2", level1Err)
+ level3Err := New(TransactionErrorCodeLockKeyConflict, "level 3",
level2Err)
+
+ errMsg := level3Err.Error()
+ assert.Contains(t, errMsg, "level 3")
+ assert.Contains(t, errMsg, "level 2")
+}
+
+// TestSeataError_MultipleInstancesIndependent tests that multiple error
instances are independent
+func TestSeataError_MultipleInstancesIndependent(t *testing.T) {
+ err1 := New(TransactionErrorCodeBeginFailed, "error 1", nil)
+ err2 := New(TransactionErrorCodeLockKeyConflict, "error 2", nil)
+
+ // Modify err1 should not affect err2
+ err1.Message = "modified"
+ assert.Equal(t, "modified", err1.Message)
+ assert.Equal(t, "error 2", err2.Message)
+}
+
+// TestSeataError_ZeroValue tests zero value of SeataError
+func TestSeataError_ZeroValue(t *testing.T) {
+ var err SeataError
+
+ assert.Equal(t, TransactionErrorCode(0), err.Code)
+ assert.Equal(t, "", err.Message)
+ assert.Nil(t, err.Parent)
+
+ // Should not panic on zero value
+ assert.NotPanics(t, func() {
+ _ = err.Error()
+ })
+}
+
+// TestSeataError_PointerVsValue tests pointer vs value receiver behavior
+func TestSeataError_PointerVsValue(t *testing.T) {
+ // Value type
+ errValue := SeataError{
+ Code: TransactionErrorCodeBeginFailed,
+ Message: "value error",
+ Parent: nil,
+ }
+
+ // Pointer type
+ errPointer := &SeataError{
+ Code: TransactionErrorCodeBeginFailed,
+ Message: "pointer error",
+ Parent: nil,
+ }
+
+ // Both should implement error interface
+ var _ error = errValue
+ var _ error = errPointer
+
+ assert.Contains(t, errValue.Error(), "value error")
+ assert.Contains(t, errPointer.Error(), "pointer error")
+}
+
+// TestNew_WithDifferentParentTypes tests New with various parent error types
+func TestNew_WithDifferentParentTypes(t *testing.T) {
+ tests := []struct {
+ name string
+ parent error
+ }{
+ {
+ name: "standard error",
+ parent: errors.New("standard error"),
+ },
+ {
+ name: "formatted error",
+ parent: fmt.Errorf("formatted: %s", "error"),
+ },
+ {
+ name: "wrapped error",
+ parent: fmt.Errorf("wrapped: %w", errors.New("inner")),
+ },
+ {
+ name: "seata error as parent",
+ parent: New(TransactionErrorCodeIO, "parent seata
error", nil),
+ },
+ {
+ name: "nil parent",
+ parent: nil,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := New(TransactionErrorCodeBeginFailed, "test",
tt.parent)
+ assert.NotNil(t, err)
+ assert.Equal(t, tt.parent, err.Parent)
+ assert.NotPanics(t, func() {
+ _ = err.Error()
+ })
+ })
+ }
+}
+
+// TestSeataError_ConcurrentAccess tests concurrent access to SeataError
+func TestSeataError_ConcurrentAccess(t *testing.T) {
+ err := New(TransactionErrorCodeBeginFailed, "concurrent test", nil)
+
+ done := make(chan bool)
+
+ // Multiple goroutines reading the error
+ for i := 0; i < 10; i++ {
+ go func() {
+ _ = err.Error()
+ _ = err.Code
+ _ = err.Message
+ done <- true
+ }()
+ }
+
+ // Wait for all goroutines
+ for i := 0; i < 10; i++ {
+ <-done
+ }
+}
+
+// TestSeataError_ErrorStringFormat tests the exact format of error string
+func TestSeataError_ErrorStringFormat(t *testing.T) {
+ tests := []struct {
+ name string
+ code TransactionErrorCode
+ message string
+ parent error
+ contains []string
+ }{
+ {
+ name: "format check with parent",
+ code: TransactionErrorCode(42),
+ message: "test message",
+ parent: errors.New("parent message"),
+ contains: []string{
+ "SeataError code 42",
+ "msg test message",
+ "parent msg is parent message",
+ },
+ },
+ {
+ name: "format check without parent",
+ code: TransactionErrorCode(99),
+ message: "another test",
+ parent: nil,
+ contains: []string{
+ "SeataError code 99",
+ "msg another test",
+ "parent msg is",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := New(tt.code, tt.message, tt.parent)
+ errStr := err.Error()
+
+ for _, substr := range tt.contains {
+ assert.Contains(t, errStr, substr)
+ }
+ })
+ }
+}
+
+// BenchmarkSeataError_Error benchmarks the Error() method
+func BenchmarkSeataError_Error(b *testing.B) {
+ err := New(TransactionErrorCodeBeginFailed, "benchmark message",
errors.New("parent"))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = err.Error()
+ }
+}
+
+// BenchmarkNew benchmarks the New() constructor
+func BenchmarkNew(b *testing.B) {
+ parent := errors.New("parent error")
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = New(TransactionErrorCodeBeginFailed, "message", parent)
+ }
+}
+
+// BenchmarkSeataError_ErrorNoParent benchmarks Error() without parent
+func BenchmarkSeataError_ErrorNoParent(b *testing.B) {
+ err := New(TransactionErrorCodeBeginFailed, "benchmark message", nil)
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = err.Error()
+ }
+}
+
+// BenchmarkNew_NoParent benchmarks New() without parent
+func BenchmarkNew_NoParent(b *testing.B) {
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = New(TransactionErrorCodeBeginFailed, "message", nil)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]