node_traffic_sync_job_test.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package job
  2. import (
  3. "sync"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. func TestAtomicBool_DefaultIsFalse(t *testing.T) {
  8. var a atomicBool
  9. if a.takeAndReset() {
  10. t.Fatal("default atomicBool should report false")
  11. }
  12. }
  13. func TestAtomicBool_SetThenTakeReturnsTrueOnce(t *testing.T) {
  14. var a atomicBool
  15. a.set()
  16. if !a.takeAndReset() {
  17. t.Fatal("takeAndReset after set should return true")
  18. }
  19. if a.takeAndReset() {
  20. t.Fatal("second takeAndReset should return false (state was reset)")
  21. }
  22. }
  23. func TestAtomicBool_SetIsIdempotent(t *testing.T) {
  24. var a atomicBool
  25. a.set()
  26. a.set()
  27. a.set()
  28. if !a.takeAndReset() {
  29. t.Fatal("repeated set should still leave the flag true")
  30. }
  31. if a.takeAndReset() {
  32. t.Fatal("flag should be cleared after the first take")
  33. }
  34. }
  35. func TestAtomicBool_ConcurrentSettersExactlyOneTakeWins(t *testing.T) {
  36. var a atomicBool
  37. const setters = 100
  38. const readers = 20
  39. var wg sync.WaitGroup
  40. for range setters {
  41. wg.Go(func() {
  42. a.set()
  43. })
  44. }
  45. wg.Wait()
  46. trueCount := 0
  47. var rwg sync.WaitGroup
  48. var mu sync.Mutex
  49. for range readers {
  50. rwg.Go(func() {
  51. if a.takeAndReset() {
  52. mu.Lock()
  53. trueCount++
  54. mu.Unlock()
  55. }
  56. })
  57. }
  58. rwg.Wait()
  59. if trueCount != 1 {
  60. t.Fatalf("expected exactly one reader to observe true, got %d", trueCount)
  61. }
  62. }
  63. // Regression (#6283): a node onboarded in selected mode with an empty tag
  64. // list empties its snapshot via FilterNodeSnapshot before the merge sees it,
  65. // so that sync adopts nothing and must not stamp InboundsAdoptedAt.
  66. func TestSyncCanAdoptInbounds(t *testing.T) {
  67. cases := []struct {
  68. name string
  69. node *model.Node
  70. aliases []string
  71. expected bool
  72. }{
  73. {"all mode always adopts", &model.Node{InboundSyncMode: "all"}, nil, true},
  74. {
  75. "selected with tags adopts",
  76. &model.Node{InboundSyncMode: "selected", InboundTags: []string{"in-443-tcp"}},
  77. nil,
  78. true,
  79. },
  80. {
  81. "selected empty with adopted alias adopts",
  82. &model.Node{InboundSyncMode: "selected"},
  83. []string{"in-443-tcp"},
  84. true,
  85. },
  86. {
  87. // The reported bug: registering in selected mode and choosing
  88. // tags afterwards stamped adoption while adopting nothing.
  89. "selected empty with no aliases adopts nothing",
  90. &model.Node{InboundSyncMode: "selected"},
  91. nil,
  92. false,
  93. },
  94. }
  95. for _, c := range cases {
  96. t.Run(c.name, func(t *testing.T) {
  97. if got := syncCanAdoptInbounds(c.node, c.aliases); got != c.expected {
  98. t.Fatalf("syncCanAdoptInbounds(%+v, %v) = %v, want %v", c.node, c.aliases, got, c.expected)
  99. }
  100. })
  101. }
  102. }