service_map_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright (C) 2023 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "context"
  9. "strings"
  10. "testing"
  11. "github.com/syncthing/syncthing/lib/events"
  12. "github.com/thejerf/suture/v4"
  13. )
  14. func TestServiceMap(t *testing.T) {
  15. t.Parallel()
  16. ctx, cancel := context.WithCancel(context.Background())
  17. t.Cleanup(cancel)
  18. sup := suture.NewSimple("TestServiceMap")
  19. sup.ServeBackground(ctx)
  20. t.Run("SimpleAddRemove", func(t *testing.T) {
  21. t.Parallel()
  22. sm := newServiceMap[string, *dummyService](events.NoopLogger)
  23. sup.Add(sm)
  24. // Add two services. They should start.
  25. d1 := newDummyService()
  26. d2 := newDummyService()
  27. sm.Add("d1", d1)
  28. sm.Add("d2", d2)
  29. <-d1.started
  30. <-d2.started
  31. // Remove them. They should stop.
  32. if !sm.Remove("d1") {
  33. t.Errorf("Remove failed")
  34. }
  35. if !sm.Remove("d2") {
  36. t.Errorf("Remove failed")
  37. }
  38. <-d1.stopped
  39. <-d2.stopped
  40. })
  41. t.Run("OverwriteImpliesRemove", func(t *testing.T) {
  42. t.Parallel()
  43. sm := newServiceMap[string, *dummyService](events.NoopLogger)
  44. sup.Add(sm)
  45. d1 := newDummyService()
  46. d2 := newDummyService()
  47. // Add d1, it should start.
  48. sm.Add("k", d1)
  49. <-d1.started
  50. // Add d2, with the same key. The previous one should stop as we're
  51. // doing a replace.
  52. sm.Add("k", d2)
  53. <-d1.stopped
  54. <-d2.started
  55. if !sm.Remove("k") {
  56. t.Errorf("Remove failed")
  57. }
  58. <-d2.stopped
  59. })
  60. t.Run("IterateWithRemoveAndWait", func(t *testing.T) {
  61. t.Parallel()
  62. sm := newServiceMap[string, *dummyService](events.NoopLogger)
  63. sup.Add(sm)
  64. // Add four services.
  65. d1 := newDummyService()
  66. d2 := newDummyService()
  67. d3 := newDummyService()
  68. d4 := newDummyService()
  69. sm.Add("keep1", d1)
  70. sm.Add("remove2", d2)
  71. sm.Add("keep3", d3)
  72. sm.Add("remove4", d4)
  73. <-d1.started
  74. <-d2.started
  75. <-d3.started
  76. <-d4.started
  77. // Remove two of them from within the iterator.
  78. sm.Each(func(k string, v *dummyService) error {
  79. if strings.HasPrefix(k, "remove") {
  80. sm.RemoveAndWait(k, 0)
  81. }
  82. return nil
  83. })
  84. // They should have stopped.
  85. <-d2.stopped
  86. <-d4.stopped
  87. // They should not be in the map anymore.
  88. if _, ok := sm.Get("remove2"); ok {
  89. t.Errorf("Service still in map")
  90. }
  91. if _, ok := sm.Get("remove4"); ok {
  92. t.Errorf("Service still in map")
  93. }
  94. // The other two should still be running.
  95. if _, ok := sm.Get("keep1"); !ok {
  96. t.Errorf("Service not in map")
  97. }
  98. if _, ok := sm.Get("keep3"); !ok {
  99. t.Errorf("Service not in map")
  100. }
  101. })
  102. }
  103. type dummyService struct {
  104. started chan struct{}
  105. stopped chan struct{}
  106. }
  107. func newDummyService() *dummyService {
  108. return &dummyService{
  109. started: make(chan struct{}),
  110. stopped: make(chan struct{}),
  111. }
  112. }
  113. func (d *dummyService) Serve(ctx context.Context) error {
  114. close(d.started)
  115. defer close(d.stopped)
  116. <-ctx.Done()
  117. return nil
  118. }