syncthing/lib/model/service_map_test.go
Jakob Borg 5118538179
lib/model: Refactor folderRunners to use a serviceMap (#9071)
Instead of separately tracking the token.

Also changes serviceMap to have a channel version of RemoveAndWait, so
that it's possible to do the removal under a lock but wait outside of
the lock. And changed where we do that in connection close, reversing
the change that happened when I added the serviceMap in 40b3b9ad1.
2023-09-02 16:42:46 +02:00

158 lines
2.9 KiB
Go

// Copyright (C) 2023 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"context"
"strings"
"testing"
"github.com/syncthing/syncthing/lib/events"
"github.com/thejerf/suture/v4"
)
func TestServiceMap(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
sup := suture.NewSimple("TestServiceMap")
sup.ServeBackground(ctx)
t.Run("SimpleAddRemove", func(t *testing.T) {
t.Parallel()
sm := newServiceMap[string, *dummyService](events.NoopLogger)
sup.Add(sm)
// Add two services. They should start.
d1 := newDummyService()
d2 := newDummyService()
sm.Add("d1", d1)
sm.Add("d2", d2)
<-d1.started
<-d2.started
// Remove them. They should stop.
if !sm.Remove("d1") {
t.Errorf("Remove failed")
}
if !sm.Remove("d2") {
t.Errorf("Remove failed")
}
<-d1.stopped
<-d2.stopped
})
t.Run("OverwriteImpliesRemove", func(t *testing.T) {
t.Parallel()
sm := newServiceMap[string, *dummyService](events.NoopLogger)
sup.Add(sm)
d1 := newDummyService()
d2 := newDummyService()
// Add d1, it should start.
sm.Add("k", d1)
<-d1.started
// Add d2, with the same key. The previous one should stop as we're
// doing a replace.
sm.Add("k", d2)
<-d1.stopped
<-d2.started
if !sm.Remove("k") {
t.Errorf("Remove failed")
}
<-d2.stopped
})
t.Run("IterateWithRemoveAndWait", func(t *testing.T) {
t.Parallel()
sm := newServiceMap[string, *dummyService](events.NoopLogger)
sup.Add(sm)
// Add four services.
d1 := newDummyService()
d2 := newDummyService()
d3 := newDummyService()
d4 := newDummyService()
sm.Add("keep1", d1)
sm.Add("remove2", d2)
sm.Add("keep3", d3)
sm.Add("remove4", d4)
<-d1.started
<-d2.started
<-d3.started
<-d4.started
// Remove two of them from within the iterator.
sm.Each(func(k string, v *dummyService) error {
if strings.HasPrefix(k, "remove") {
sm.RemoveAndWait(k, 0)
}
return nil
})
// They should have stopped.
<-d2.stopped
<-d4.stopped
// They should not be in the map anymore.
if _, ok := sm.Get("remove2"); ok {
t.Errorf("Service still in map")
}
if _, ok := sm.Get("remove4"); ok {
t.Errorf("Service still in map")
}
// The other two should still be running.
if _, ok := sm.Get("keep1"); !ok {
t.Errorf("Service not in map")
}
if _, ok := sm.Get("keep3"); !ok {
t.Errorf("Service not in map")
}
})
}
type dummyService struct {
started chan struct{}
stopped chan struct{}
}
func newDummyService() *dummyService {
return &dummyService{
started: make(chan struct{}),
stopped: make(chan struct{}),
}
}
func (d *dummyService) Serve(ctx context.Context) error {
close(d.started)
defer close(d.stopped)
<-ctx.Done()
return nil
}