equal_but_not_same.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package testutil
  2. import (
  3. "reflect"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. )
  7. // EqualButNotSame asserts that expected and actual objects are not the same.
  8. // It recursively checks all fields to ensure that no pointers are shared.
  9. // If a pointer, slice or map are nil in either object, the test fails.
  10. func EqualButNotSame(t *testing.T, expected, actual any) {
  11. t.Helper()
  12. expectedVal := reflect.ValueOf(expected)
  13. actualVal := reflect.ValueOf(actual)
  14. deepEqual(t, expectedVal, actualVal, "")
  15. }
  16. // deepEqual recursively verifies that all values are equal but pointers are different
  17. // except for the Expires field which is explicitly allowed to be shared
  18. func deepEqual(t *testing.T, left, right reflect.Value, fieldPath string) {
  19. require.True(t, left.IsValid() && right.IsValid(), "invalid value at %s", fieldPath)
  20. require.Equal(t, left.Type(), right.Type(), "types are not equal at %s", fieldPath)
  21. switch left.Kind() {
  22. case reflect.Ptr:
  23. // Pointers should not be nil and must point to different objects
  24. require.False(t, left.IsNil(), "nil pointer at %s (left)", fieldPath)
  25. require.False(t, right.IsNil(), "nil pointer at %s (right)", fieldPath)
  26. require.NotSame(t, left.Interface(), right.Interface(), "shared pointer at %s", fieldPath)
  27. deepEqual(t, left.Elem(), right.Elem(), fieldPath)
  28. case reflect.Slice:
  29. // Slices should contain some elements and must not share the same underlying array
  30. require.Equal(t, left.Len(), right.Len(), "slice length mismatch at %s", fieldPath)
  31. require.NotEmpty(t, left.Len(), "slice must not be empty %s (left)", fieldPath)
  32. require.NotEmpty(t, right.Len(), "slice must not be empty %s (right)", fieldPath)
  33. require.NotEqual(t, left.Pointer(), right.Pointer(), "shared slices at %s", fieldPath)
  34. // Recursively verify slice elements
  35. for i := 0; i < left.Len(); i++ {
  36. elemPath := buildPath(fieldPath, "[", anyToString(i), "]")
  37. deepEqual(t, left.Index(i), right.Index(i), elemPath)
  38. }
  39. case reflect.Map:
  40. // Maps should contain some elements and must not share the same underlying map
  41. require.Equal(t, left.Len(), right.Len(), "map length mismatch at %s", fieldPath)
  42. require.NotEmpty(t, left.Len(), "map must not be empty %s (left)", fieldPath)
  43. require.NotEmpty(t, right.Len(), "map must not be empty %s (right)", fieldPath)
  44. require.NotEqual(t, left.Pointer(), right.Pointer(), "shared maps at %s", fieldPath)
  45. // Recursively verify map values
  46. for _, key := range left.MapKeys() {
  47. keyStr := anyToString(key.Interface())
  48. keyPath := buildPath(fieldPath, "[", keyStr, "]")
  49. originalMapVal := left.MapIndex(key)
  50. clonedMapVal := right.MapIndex(key)
  51. deepEqual(t, originalMapVal, clonedMapVal, keyPath)
  52. }
  53. case reflect.Struct:
  54. require.Equal(t, left.Interface(), right.Interface(), "structs are not equal at %s", fieldPath)
  55. // Fallback to recursive field-by-field comparison
  56. for i := 0; i < left.NumField(); i++ {
  57. field := left.Type().Field(i)
  58. if !field.IsExported() {
  59. continue // Skip unexported fields
  60. }
  61. nestedPath := buildPath(fieldPath, ".", field.Name, "")
  62. originalFieldVal := left.Field(i)
  63. clonedFieldVal := right.Field(i)
  64. deepEqual(t, originalFieldVal, clonedFieldVal, nestedPath)
  65. }
  66. default:
  67. // For primitive types, just verify equality
  68. require.Equal(t, left.Interface(), right.Interface(), "values not equal at %s", fieldPath)
  69. }
  70. }
  71. // buildPath builds a field path for error messages
  72. func buildPath(basePath, separator, element, suffix string) string {
  73. if basePath == "" {
  74. return element + suffix
  75. }
  76. return basePath + separator + element + suffix
  77. }
  78. // anyToString converts an any to a string for path building
  79. func anyToString(v any) string {
  80. switch val := v.(type) {
  81. case string:
  82. return val
  83. default:
  84. return reflect.ValueOf(val).String()
  85. }
  86. }
  87. // ........-------------------============++===============+=+==+++++++++++++++++++
  88. // .........------------------==-=====#%%%%%%%%%#+============++==+==++++++++++++++
  89. // .........--------------------===++#%##%%%##%#####===========+==+=++=++=+++++=+++
  90. // ..........-----------------=-=+#%#####+++=-=-++#%#===========+==+==++=+==++++=+
  91. // ..........-----------------==###+#####++=---.. .=++==============+===+=+=+=++=+
  92. // .........-.---------------=--+=-=####++==---.. -=================++=+==++=++
  93. // .. ........-.--------------==+. -#####+==---. ====================++==+++=
  94. // .. ......---.-----------==-==- .+#++++=----. =====================++==++=
  95. // ........--.-------------===-. -#%%%#+-=-=---. -=====================++==++
  96. // ...........------------==++%= .+#+=+%#+#.=-.-- =====================+==++=+
  97. // .........--------------===### ++=#%=%+#+ -+#+=. .--====================+==+==
  98. // ........----------------===%# +.+###%+#- =+##.=. -===================++=++++
  99. // ........---------------====## ++#%%##+#= -=- .==================++++++=++
  100. // .......----------------===--+ .+#%#++++#= .. =========+=====+++=++=++++++
  101. // .....----------------=-====-- -+==+%%#%#+- = ============+==++++=++==++=+
  102. // ..----------------==========- -- #%%#%#+=. =- ========+===+=++=++++++++++++
  103. // .----------------==-=========. ==-%#++#=-. =. ====+=====+==+==+++==++=++++++
  104. // .--------------=--============.=-#%@%%%#+++--= ===+==++==++=+=+==+++++++++++++
  105. // -------------=-================-=#####%#+- .- ==++++++=++=+++++++++++++++++++
  106. // -------------=================- -.+%##+-.+-. . .+=++=++++++=+++++++++++++++++
  107. // ---------==-================= = --.+%%#=. =+++++++++++++++++++++++++++
  108. // -------=---===============. #= =+-==#++++. . -++++++++++++++++++++++++
  109. // -----=-==============- .. +#= =%###++ .- .=+++++++++++++++++++
  110. // --==-===========- .-==.. -### --++. +. . .=+++++++++++++++
  111. // ============- .-=+##=++-. =#%# =+%##+ #=. =++++++++++
  112. // ========== = =##+###=+===- +%%#..%+%+-+# #+- -++++++++
  113. // ========= +==##%%##++-#++- ##=++=.+###%%% +++. .. . +++++++
  114. // ========-.%++%%%%#+=+-## ++==++++=-..# -= =+#- -... . ++##+##
  115. // ======++ -%+#%%%%#+=#=# =+++===+++. . +# -+%#- .---.......... ++##+#+
  116. // ======+. -%=#%%%%#=+#+. =%%#+++##++=. .=#+=#%+. ..--..-........ - -++++##+
  117. // +++++++-..%=#%%%%#=+#..###%%#+=+++=---+.=###%=. ..--.--...--... -. ++++#++
  118. // ++=++++.%#+=#%%%#+++ =++%%@%%###%##++=.-###%+-..-------.------... .=. ++##+#
  119. // +++++++.#%%=#%%%#+ .##%%%%%%%###%@@%##%=#%%#=-.--=-=---------... +=. =#####
  120. // ++++++ .%%%#+#### ==##%%%@%%#=%@@@%%###+#%%+=--==-===--------....-#=. +####
  121. // +++++= +%%%%-#- +#+#%%%@%%%#+%@@@@%%%%%%%%+==========--------..--#+--..... .####
  122. // ++++= -.#%%%- #++##%%%%%%%#=%%@@@%#%%%%%%#+==+============------=#=--.. .... ###
  123. // +++++ .#+%% .=%### +%%#%%@%=#%@@@%%%@@%%%+++++++================%+==--.. . . ##
  124. // ++++ .==. =+##%%#%%%%%%%@%=#%@@@@%%%%@@%++++++++=++===========#%++=----. +#
  125. // +++ .+#..=###%##%#@%#%#%%%%=##@@@%#=%%@%#######++++++++++=+++++%%#++=---..- =#
  126. // +==--=.+##%%%%+=++++++#@%%%+#%@@@@%+=+#%###%#####++++++++=++++#%%%#++=---..--.-#
  127. // +%%%-+=%#@@%%%+=#%+++#%@%%%##%%@@@@%%#=++#%%%%######+++++=++++%%%%##++===-. . +
  128. // .- --%%@#@@@%%+++#%##@@@@%%%%%%%######%%##+#%######++#+++++++#%%%%##++++###+= -
  129. // ..=++%@@%@@@%%%##%#%@@@@@%%%@@@@@%%%#++++++++%###+++###++++++@@%%%%##+==--==++#=
  130. // -+#%%%@@%@@@@%%%%%@@@@@@@@%@@%%%%%%%###++++++++#####+++++++++%@%%%%######+=--..-
  131. // +##%%%%@@@@@@@%%@@@@@@@@@@@@@@@@%%%##++#%#+++++++++++#####+++%@@@%%%#++=====++=-
  132. // #%#@%@%@@@@@%%@@@@@@@@@@@@@%%%%%%%@%%#%%#####++++#+====++++++%@@@@@@@@@%#+==----
  133. // ##%%@%%@@@@%@@@@@@@@@@@@@@@@@%%%%%%%%%%%#%%%%%###%+++======+++##+=+#+++##%%#%%%+
  134. // +#%%@@@%@@@%@@%+#@@@@@@@@@@@@%%%%@@%%%%%@%%%%%%%%@#+++++===-===#++==+++=+++#%#%+
  135. // #%%@@@%%@%%%%@#+#%@@@@%%%@@@@@@@%%%%@@%%%%@%%%%%%@#######+#+==+++=++=======+=++%
  136. // ##@@@@@%%%%@#++++#%%%%%%%%@@@@@@@@@@@@@@@@@@@%%@@@##########+=++++++#++++++###+#
  137. // ++#%@%%%%%#++++#+-###%%%%%%%@@%@@@@@@@@@@@@@@@@@@@##+##+####%#########+#########
  138. // +++++++#++++##+#+ ++####%%%%%%@%@@@@@@@@@@@@@@@%%%####%%####%%#####%#%%#####%%##
  139. // +#++#++++++++#+++ -+#+##%%%%@@%%@@@@@@@@@@@@@@@@@%####%%%%%%%%%%%%##%@@%%%%#%%%%
  140. // +#+++#+#####++++= .=++##%#%%%%%%%@@@@@@@@@@@@@@@%####%%%%@%%@%%%%%%%%%@@%@%%%%%#
  141. // ####+#+++++###+#-..=++##%%%%%%%#%@@@@@@@@@@@@@@@%####%%%%%%%%@%#################
  142. // #++#++##+#++#++#..-=+###%%%%%%#+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%#################
  143. // ++#+#++#+##++##=---=++####%%%###@@@%@@@@@@%%@@@@@@@@@@@@@@@@@%%+################
  144. // +##+##++#++#++#--=+=++###%#%####@@@@@@@@@@@@@@@%@@@%%@%%%%%%###=+###############
  145. // +++++##+#++#+++.-===+#####%%####@@@@@@@@%%@@%%@@%%%%%%########+==###############
  146. // +++#++++++#+++..--==+##########%%%@@@@@%%%%%@@%%%%########%##+#=-###############
  147. // +#++#++#+#++++..--===%#########%%%%%%%@%%%%%###%##############+==+##############
  148. // ++++##+++++#++.==--=+@%##+###+#%%%%%%%%%%%####%##%%#%%%#######++==##############