@cryptotaxi247 / kubo / commits / bb58ca473

remove unneeded thirdparty packages (#10871)

* remove unneeded thirdparty packages Remove unnecessary packages from `thirdparty` in repo. - Remove `thirdparty/assert` (replaced by `github.com/stretchr/testify/require`) - Remove `thirdparty/dir` (replacd by `misc/fsutil`) - Remove `thirdparty/notifier` (unused)

Andrew Gillis committed Jul 16, 2025 at 01:53 UTC bb58ca4737cc2740901a3d3e4322fe78ebfc7d1c
11 files changed +59 -541
cmd/ipfswatch/ipfswatch_test.go
+4 -4
@@ -6,11 +6,11 @@ package main
6 import (
7 "testing"
8
9 - "github.com/ipfs/kubo/thirdparty/assert"
9 + "github.com/stretchr/testify/require"
10 )
11
12 func TestIsHidden(t *testing.T) {
13 - assert.True(IsHidden("bar/.git"), t, "dirs beginning with . should be recognized as hidden")
14 - assert.False(IsHidden("."), t, ". for current dir should not be considered hidden")
15 - assert.False(IsHidden("bar/baz"), t, "normal dirs should not be hidden")
13 + require.True(t, IsHidden("bar/.git"), "dirs beginning with . should be recognized as hidden")
14 + require.False(t, IsHidden("."), ". for current dir should not be considered hidden")
15 + require.False(t, IsHidden("bar/baz"), "normal dirs should not be hidden")
16 }
core/corehttp/p2p_proxy_test.go
+6 -11
@@ -5,9 +5,8 @@ import (
5 "strings"
6 "testing"
7
8 - "github.com/ipfs/kubo/thirdparty/assert"
9 -
8 protocol "github.com/libp2p/go-libp2p/core/protocol"
9 + "github.com/stretchr/testify/require"
10 )
11
12 type TestCase struct {
@@ -29,12 +28,10 @@ func TestParseRequest(t *testing.T) {
28 req, _ := http.NewRequest(http.MethodGet, url, strings.NewReader(""))
29
30 parsed, err := parseRequest(req)
32 - if err != nil {
33 - t.Fatal(err)
34 - }
35 - assert.True(parsed.httpPath == tc.path, t, "proxy request path")
36 - assert.True(parsed.name == protocol.ID(tc.name), t, "proxy request name")
37 - assert.True(parsed.target == tc.target, t, "proxy request peer-id")
31 + require.NoError(t, err)
32 + require.Equal(t, tc.path, parsed.httpPath, "proxy request path")
33 + require.Equal(t, protocol.ID(tc.name), parsed.name, "proxy request name")
34 + require.Equal(t, tc.target, parsed.target, "proxy request peer-id")
35 }
36 }
37
@@ -49,8 +46,6 @@ func TestParseRequestInvalidPath(t *testing.T) {
46 req, _ := http.NewRequest(http.MethodGet, url, strings.NewReader(""))
47
48 _, err := parseRequest(req)
52 - if err == nil {
53 - t.Fail()
54 - }
49 + require.Error(t, err)
50 }
51 }
docs/changelogs/v0.37.md
+9
@@ -11,6 +11,7 @@ This release was brought to you by the [Interplanetary Shipyard](https://ipship
11 - [Overview](#overview)
12 - [🔦 Highlights](#-highlights)
13 - [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes)
14 + - [Remove unnecessary packages from thirdparty](#remove-unnecessary-packages-from-thirdparty)
15 - [📦️ Important dependency updates](#-important-dependency-updates)
16 - [📝 Changelog](#-changelog)
17 - [👨‍👩‍👧‍👦 Contributors](#-contributors)
@@ -30,6 +31,14 @@ A new `ipfs provide clear` command also allows manual queue clearing for debuggi
31 > [!NOTE]
32 > Upgrading to Kubo 0.37 will automatically clear any preexisting provide queue. The next time `Reprovider.Interval` hits, `Reprovider.Strategy` will be executed on a clean slate, ensuring consistent behavior with your current configuration.
33
34 +#### Remove unnecessary packages from thirdparty
35 +
36 +Removed unnecessary packages from the `thirdparty` area of kubo repositroy.
37 +
38 +- Removed `thirdparty/assert` (replaced by `github.com/stretchr/testify/require`)
39 +- Removed `thirdparty/dir` (replaced by `misc/fsutil)`
40 +- Removed `thirdparty/notifier` (unused)
41 +
42 #### 📦️ Important dependency updates
43
44 - update `boxo` to [v0.34.0](https://github.com/ipfs/boxo/releases/tag/v0.34.0)
repo/common/common_test.go
+6 -6
@@ -3,7 +3,7 @@ package common
3 import (
4 "testing"
5
6 - "github.com/ipfs/kubo/thirdparty/assert"
6 + "github.com/stretchr/testify/require"
7 )
8
9 func TestMapMergeDeepReturnsNew(t *testing.T) {
@@ -15,7 +15,7 @@ func TestMapMergeDeepReturnsNew(t *testing.T) {
15
16 MapMergeDeep(leftMap, rightMap)
17
18 - assert.True(leftMap["A"] == "Hello World", t, "MapMergeDeep should return a new map instance")
18 + require.Equal(t, "Hello World", leftMap["A"], "MapMergeDeep should return a new map instance")
19 }
20
21 func TestMapMergeDeepNewKey(t *testing.T) {
@@ -46,7 +46,7 @@ func TestMapMergeDeepNewKey(t *testing.T) {
46 }
47 */
48
49 - assert.True(result["B"] == "Bar", t, "New keys in right map should exist in resulting map")
49 + require.Equal(t, "Bar", result["B"], "New keys in right map should exist in resulting map")
50 }
51
52 func TestMapMergeDeepRecursesOnMaps(t *testing.T) {
@@ -92,8 +92,8 @@ func TestMapMergeDeepRecursesOnMaps(t *testing.T) {
92 */
93
94 resultA := result["A"].(map[string]interface{})
95 - assert.True(resultA["B"] == "A value!", t, "Unaltered values should not change")
96 - assert.True(resultA["C"] == "A different value!", t, "Nested values should be altered")
95 + require.Equal(t, "A value!", resultA["B"], "Unaltered values should not change")
96 + require.Equal(t, "A different value!", resultA["C"], "Nested values should be altered")
97 }
98
99 func TestMapMergeDeepRightNotAMap(t *testing.T) {
@@ -128,5 +128,5 @@ func TestMapMergeDeepRightNotAMap(t *testing.T) {
128 }
129 */
130
131 - assert.True(result["A"] == "Not a map!", t, "Right values that are not a map should be set on the result")
131 + require.Equal(t, "Not a map!", result["A"], "Right values that are not a map should be set on the result")
132 }
repo/fsrepo/fsrepo.go
+1 -2
@@ -16,7 +16,6 @@ import (
16 keystore "github.com/ipfs/boxo/keystore"
17 repo "github.com/ipfs/kubo/repo"
18 "github.com/ipfs/kubo/repo/common"
19 - dir "github.com/ipfs/kubo/thirdparty/dir"
19 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
20
21 ds "github.com/ipfs/go-datastore"
@@ -192,7 +191,7 @@ func open(repoPath string, userConfigFilePath string) (repo.Repo, error) {
191 }
192
193 // check repo path, then check all constituent parts.
195 - if err := dir.Writable(r.path); err != nil {
194 + if err := fsutil.DirWritable(r.path); err != nil {
195 return nil, err
196 }
197
repo/fsrepo/fsrepo_test.go
+32 -33
@@ -7,17 +7,16 @@ import (
7 "path/filepath"
8 "testing"
9
10 - "github.com/ipfs/kubo/thirdparty/assert"
11 -
10 datastore "github.com/ipfs/go-datastore"
11 config "github.com/ipfs/kubo/config"
12 + "github.com/stretchr/testify/require"
13 )
14
15 func TestInitIdempotence(t *testing.T) {
16 t.Parallel()
17 path := t.TempDir()
18 for i := 0; i < 10; i++ {
20 - assert.Nil(Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t, "multiple calls to init should succeed")
19 + require.NoError(t, Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), "multiple calls to init should succeed")
20 }
21 }
22
@@ -32,78 +31,78 @@ func TestCanManageReposIndependently(t *testing.T) {
31 pathB := t.TempDir()
32
33 t.Log("initialize two repos")
35 - assert.Nil(Init(pathA, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t, "a", "should initialize successfully")
36 - assert.Nil(Init(pathB, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t, "b", "should initialize successfully")
34 + require.NoError(t, Init(pathA, &config.Config{Datastore: config.DefaultDatastoreConfig()}), "a", "should initialize successfully")
35 + require.NoError(t, Init(pathB, &config.Config{Datastore: config.DefaultDatastoreConfig()}), "b", "should initialize successfully")
36
37 t.Log("ensure repos initialized")
39 - assert.True(IsInitialized(pathA), t, "a should be initialized")
40 - assert.True(IsInitialized(pathB), t, "b should be initialized")
38 + require.True(t, IsInitialized(pathA), "a should be initialized")
39 + require.True(t, IsInitialized(pathB), "b should be initialized")
40
41 t.Log("open the two repos")
42 repoA, err := Open(pathA)
44 - assert.Nil(err, t, "a")
43 + require.NoError(t, err, "a")
44 repoB, err := Open(pathB)
46 - assert.Nil(err, t, "b")
45 + require.NoError(t, err, "b")
46
47 t.Log("close and remove b while a is open")
49 - assert.Nil(repoB.Close(), t, "close b")
50 - assert.Nil(Remove(pathB), t, "remove b")
48 + require.NoError(t, repoB.Close(), "close b")
49 + require.NoError(t, Remove(pathB), "remove b")
50
51 t.Log("close and remove a")
53 - assert.Nil(repoA.Close(), t)
54 - assert.Nil(Remove(pathA), t)
52 + require.NoError(t, repoA.Close())
53 + require.NoError(t, Remove(pathA))
54 }
55
56 func TestDatastoreGetNotAllowedAfterClose(t *testing.T) {
57 t.Parallel()
58 path := t.TempDir()
59
61 - assert.True(!IsInitialized(path), t, "should NOT be initialized")
62 - assert.Nil(Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t, "should initialize successfully")
60 + require.False(t, IsInitialized(path), "should NOT be initialized")
61 + require.NoError(t, Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), "should initialize successfully")
62 r, err := Open(path)
64 - assert.Nil(err, t, "should open successfully")
63 + require.NoError(t, err, "should open successfully")
64
65 k := "key"
66 data := []byte(k)
68 - assert.Nil(r.Datastore().Put(context.Background(), datastore.NewKey(k), data), t, "Put should be successful")
67 + require.NoError(t, r.Datastore().Put(context.Background(), datastore.NewKey(k), data), "Put should be successful")
68
70 - assert.Nil(r.Close(), t)
69 + require.NoError(t, r.Close())
70 _, err = r.Datastore().Get(context.Background(), datastore.NewKey(k))
72 - assert.Err(err, t, "after closer, Get should be fail")
71 + require.Error(t, err, "after closer, Get should be fail")
72 }
73
74 func TestDatastorePersistsFromRepoToRepo(t *testing.T) {
75 t.Parallel()
76 path := t.TempDir()
77
79 - assert.Nil(Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t)
78 + require.NoError(t, Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}))
79 r1, err := Open(path)
81 - assert.Nil(err, t)
80 + require.NoError(t, err)
81
82 k := "key"
83 expected := []byte(k)
85 - assert.Nil(r1.Datastore().Put(context.Background(), datastore.NewKey(k), expected), t, "using first repo, Put should be successful")
86 - assert.Nil(r1.Close(), t)
84 + require.NoError(t, r1.Datastore().Put(context.Background(), datastore.NewKey(k), expected), "using first repo, Put should be successful")
85 + require.NoError(t, r1.Close())
86
87 r2, err := Open(path)
89 - assert.Nil(err, t)
88 + require.NoError(t, err)
89 actual, err := r2.Datastore().Get(context.Background(), datastore.NewKey(k))
91 - assert.Nil(err, t, "using second repo, Get should be successful")
92 - assert.Nil(r2.Close(), t)
93 - assert.True(bytes.Equal(expected, actual), t, "data should match")
90 + require.NoError(t, err, "using second repo, Get should be successful")
91 + require.NoError(t, r2.Close())
92 + require.True(t, bytes.Equal(expected, actual), "data should match")
93 }
94
95 func TestOpenMoreThanOnceInSameProcess(t *testing.T) {
96 t.Parallel()
97 path := t.TempDir()
99 - assert.Nil(Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}), t)
98 + require.NoError(t, Init(path, &config.Config{Datastore: config.DefaultDatastoreConfig()}))
99
100 r1, err := Open(path)
102 - assert.Nil(err, t, "first repo should open successfully")
101 + require.NoError(t, err, "first repo should open successfully")
102 r2, err := Open(path)
104 - assert.Nil(err, t, "second repo should open successfully")
105 - assert.True(r1 == r2, t, "second open returns same value")
103 + require.NoError(t, err, "second repo should open successfully")
104 + require.Equal(t, r1, r2, "second open returns same value")
105
107 - assert.Nil(r1.Close(), t)
108 - assert.Nil(r2.Close(), t)
106 + require.NoError(t, r1.Close())
107 + require.NoError(t, r2.Close())
108 }
thirdparty/README.md
+1 -4
@@ -1,5 +1,2 @@
1 -thirdparty consists of Golang packages that contain no go-ipfs dependencies and
2 -may be vendored ipfs/go-ipfs at a later date.
3 -
1 packages under this directory _must not_ import packages under
5 -`ipfs/go-ipfs` that are not also under `thirdparty`.
2 +`ipfs/kubo` that are not also under `thirdparty`.
thirdparty/assert/assert.go deleted
-25
@@ -1,25 +0,0 @@
1 -package assert
2 -
3 -import "testing"
4 -
5 -func Nil(err error, t *testing.T, msgs ...string) {
6 - if err != nil {
7 - t.Fatal(msgs, "error:", err)
8 - }
9 -}
10 -
11 -func True(v bool, t *testing.T, msgs ...string) {
12 - if !v {
13 - t.Fatal(msgs)
14 - }
15 -}
16 -
17 -func False(v bool, t *testing.T, msgs ...string) {
18 - True(!v, t, msgs...)
19 -}
20 -
21 -func Err(err error, t *testing.T, msgs ...string) {
22 - if err == nil {
23 - t.Fatal(msgs, "error:", err)
24 - }
25 -}
thirdparty/dir/dir.go deleted
-25
@@ -1,25 +0,0 @@
1 -package dir
2 -
3 -// TODO move somewhere generic
4 -
5 -import (
6 - "errors"
7 - "os"
8 - "path/filepath"
9 -)
10 -
11 -// Writable ensures the directory exists and is writable.
12 -func Writable(path string) error {
13 - // Construct the path if missing
14 - if err := os.MkdirAll(path, os.ModePerm); err != nil {
15 - return err
16 - }
17 - // Check the directory is writable
18 - if f, err := os.Create(filepath.Join(path, "._check_writable")); err == nil {
19 - f.Close()
20 - os.Remove(f.Name())
21 - } else {
22 - return errors.New("'" + path + "' is not writable")
23 - }
24 - return nil
25 -}
thirdparty/notifier/notifier.go deleted
-142
@@ -1,142 +0,0 @@
1 -// Package notifier provides a simple notification dispatcher
2 -// meant to be embedded in larger structures who wish to allow
3 -// clients to sign up for event notifications.
4 -package notifier
5 -
6 -import (
7 - "sync"
8 -
9 - process "github.com/jbenet/goprocess"
10 - ratelimit "github.com/jbenet/goprocess/ratelimit"
11 -)
12 -
13 -// Notifiee is a generic interface. Clients implement
14 -// their own Notifiee interfaces to ensure type-safety
15 -// of notifications:
16 -//
17 -// type RocketNotifiee interface{
18 -// Countdown(r Rocket, countdown time.Duration)
19 -// LiftedOff(Rocket)
20 -// ReachedOrbit(Rocket)
21 -// Detached(Rocket, Capsule)
22 -// Landed(Rocket)
23 -// }
24 -type Notifiee interface{}
25 -
26 -// Notifier is a notification dispatcher. It's meant
27 -// to be composed, and its zero-value is ready to be used.
28 -//
29 -// type Rocket struct {
30 -// notifier notifier.Notifier
31 -// }
32 -type Notifier struct {
33 - mu sync.RWMutex // guards notifiees
34 - nots map[Notifiee]struct{}
35 - lim *ratelimit.RateLimiter
36 -}
37 -
38 -// RateLimited returns a rate limited Notifier. only limit goroutines
39 -// will be spawned. If limit is zero, no rate limiting happens. This
40 -// is the same as `Notifier{}`.
41 -func RateLimited(limit int) *Notifier {
42 - n := &Notifier{}
43 - if limit > 0 {
44 - n.lim = ratelimit.NewRateLimiter(process.Background(), limit)
45 - }
46 - return n
47 -}
48 -
49 -// Notify signs up Notifiee e for notifications. This function
50 -// is meant to be called behind your own type-safe function(s):
51 -//
52 -// // generic function for pattern-following
53 -// func (r *Rocket) Notify(n Notifiee) {
54 -// r.notifier.Notify(n)
55 -// }
56 -//
57 -// // or as part of other functions
58 -// func (r *Rocket) Onboard(a Astronaut) {
59 -// r.astronauts = append(r.austronauts, a)
60 -// r.notifier.Notify(a)
61 -// }
62 -func (n *Notifier) Notify(e Notifiee) {
63 - n.mu.Lock()
64 - if n.nots == nil { // so that zero-value is ready to be used.
65 - n.nots = make(map[Notifiee]struct{})
66 - }
67 - n.nots[e] = struct{}{}
68 - n.mu.Unlock()
69 -}
70 -
71 -// StopNotify stops notifying Notifiee e. This function
72 -// is meant to be called behind your own type-safe function(s):
73 -//
74 -// // generic function for pattern-following
75 -// func (r *Rocket) StopNotify(n Notifiee) {
76 -// r.notifier.StopNotify(n)
77 -// }
78 -//
79 -// // or as part of other functions
80 -// func (r *Rocket) Detach(c Capsule) {
81 -// r.notifier.StopNotify(c)
82 -// r.capsule = nil
83 -// }
84 -func (n *Notifier) StopNotify(e Notifiee) {
85 - n.mu.Lock()
86 - if n.nots != nil { // so that zero-value is ready to be used.
87 - delete(n.nots, e)
88 - }
89 - n.mu.Unlock()
90 -}
91 -
92 -// NotifyAll messages the notifier's notifiees with a given notification.
93 -// This is done by calling the given function with each notifiee. It is
94 -// meant to be called with your own type-safe notification functions:
95 -//
96 -// func (r *Rocket) Launch() {
97 -// r.notifyAll(func(n Notifiee) {
98 -// n.Launched(r)
99 -// })
100 -// }
101 -//
102 -// // make it private so only you can use it. This function is necessary
103 -// // to make sure you only up-cast in one place. You control who you added
104 -// // to be a notifiee. If Go adds generics, maybe we can get rid of this
105 -// // method but for now it is like wrapping a type-less container with
106 -// // a type safe interface.
107 -// func (r *Rocket) notifyAll(notify func(Notifiee)) {
108 -// r.notifier.NotifyAll(func(n notifier.Notifiee) {
109 -// notify(n.(Notifiee))
110 -// })
111 -// }
112 -//
113 -// Note well: each notification is launched in its own goroutine, so they
114 -// can be processed concurrently, and so that whatever the notification does
115 -// it _never_ blocks out the client. This is so that consumers _cannot_ add
116 -// hooks into your object that block you accidentally.
117 -func (n *Notifier) NotifyAll(notify func(Notifiee)) {
118 - n.mu.Lock()
119 - defer n.mu.Unlock()
120 -
121 - if n.nots == nil { // so that zero-value is ready to be used.
122 - return
123 - }
124 -
125 - // no rate limiting.
126 - if n.lim == nil {
127 - for notifiee := range n.nots {
128 - go notify(notifiee)
129 - }
130 - return
131 - }
132 -
133 - // with rate limiting.
134 - n.lim.Go(func(worker process.Process) {
135 - for notifiee := range n.nots {
136 - notifiee := notifiee // rebind for loop data races
137 - n.lim.LimitedGo(func(worker process.Process) {
138 - notify(notifiee)
139 - })
140 - }
141 - })
142 -}
thirdparty/notifier/notifier_test.go deleted
-289
@@ -1,289 +0,0 @@
1 -package notifier
2 -
3 -import (
4 - "fmt"
5 - "sync"
6 - "testing"
7 - "time"
8 -)
9 -
10 -// test data structures.
11 -type Router struct {
12 - queue chan Packet
13 - notifier Notifier
14 -}
15 -
16 -type Packet struct{}
17 -
18 -type RouterNotifiee interface {
19 - Enqueued(*Router, Packet)
20 - Forwarded(*Router, Packet)
21 - Dropped(*Router, Packet)
22 -}
23 -
24 -func (r *Router) Notify(n RouterNotifiee) {
25 - r.notifier.Notify(n)
26 -}
27 -
28 -func (r *Router) StopNotify(n RouterNotifiee) {
29 - r.notifier.StopNotify(n)
30 -}
31 -
32 -func (r *Router) notifyAll(notify func(n RouterNotifiee)) {
33 - r.notifier.NotifyAll(func(n Notifiee) {
34 - notify(n.(RouterNotifiee))
35 - })
36 -}
37 -
38 -func (r *Router) Receive(p Packet) {
39 - select {
40 - case r.queue <- p: // enqueued
41 - r.notifyAll(func(n RouterNotifiee) {
42 - n.Enqueued(r, p)
43 - })
44 -
45 - default: // drop
46 - r.notifyAll(func(n RouterNotifiee) {
47 - n.Dropped(r, p)
48 - })
49 - }
50 -}
51 -
52 -func (r *Router) Forward() {
53 - p := <-r.queue
54 - r.notifyAll(func(n RouterNotifiee) {
55 - n.Forwarded(r, p)
56 - })
57 -}
58 -
59 -type Metrics struct {
60 - enqueued int
61 - forwarded int
62 - dropped int
63 - received chan struct{}
64 - sync.Mutex
65 -}
66 -
67 -func (m *Metrics) Enqueued(*Router, Packet) {
68 - m.Lock()
69 - m.enqueued++
70 - m.Unlock()
71 - if m.received != nil {
72 - m.received <- struct{}{}
73 - }
74 -}
75 -
76 -func (m *Metrics) Forwarded(*Router, Packet) {
77 - m.Lock()
78 - m.forwarded++
79 - m.Unlock()
80 - if m.received != nil {
81 - m.received <- struct{}{}
82 - }
83 -}
84 -
85 -func (m *Metrics) Dropped(*Router, Packet) {
86 - m.Lock()
87 - m.dropped++
88 - m.Unlock()
89 - if m.received != nil {
90 - m.received <- struct{}{}
91 - }
92 -}
93 -
94 -func (m *Metrics) String() string {
95 - m.Lock()
96 - defer m.Unlock()
97 - return fmt.Sprintf("%d enqueued, %d forwarded, %d in queue, %d dropped",
98 - m.enqueued, m.forwarded, m.enqueued-m.forwarded, m.dropped)
99 -}
100 -
101 -func TestNotifies(t *testing.T) {
102 - m := Metrics{received: make(chan struct{})}
103 - r := Router{queue: make(chan Packet, 10)}
104 - r.Notify(&m)
105 -
106 - for i := 0; i < 10; i++ {
107 - r.Receive(Packet{})
108 - <-m.received
109 - if m.enqueued != (1 + i) {
110 - t.Error("not notifying correctly", m.enqueued, 1+i)
111 - }
112 -
113 - }
114 -
115 - for i := 0; i < 10; i++ {
116 - r.Receive(Packet{})
117 - <-m.received
118 - if m.enqueued != 10 {
119 - t.Error("not notifying correctly", m.enqueued, 10)
120 - }
121 - if m.dropped != (1 + i) {
122 - t.Error("not notifying correctly", m.dropped, 1+i)
123 - }
124 - }
125 -}
126 -
127 -func TestStopsNotifying(t *testing.T) {
128 - m := Metrics{received: make(chan struct{})}
129 - r := Router{queue: make(chan Packet, 10)}
130 - r.Notify(&m)
131 -
132 - for i := 0; i < 5; i++ {
133 - r.Receive(Packet{})
134 - <-m.received
135 - if m.enqueued != (1 + i) {
136 - t.Error("not notifying correctly")
137 - }
138 - }
139 -
140 - r.StopNotify(&m)
141 -
142 - for i := 0; i < 5; i++ {
143 - r.Receive(Packet{})
144 - select {
145 - case <-m.received:
146 - t.Error("did not stop notifying")
147 - default:
148 - }
149 - if m.enqueued != 5 {
150 - t.Error("did not stop notifying")
151 - }
152 - }
153 -}
154 -
155 -func TestThreadsafe(t *testing.T) {
156 - N := 1000
157 - r := Router{queue: make(chan Packet, 10)}
158 - m1 := Metrics{received: make(chan struct{})}
159 - m2 := Metrics{received: make(chan struct{})}
160 - m3 := Metrics{received: make(chan struct{})}
161 - r.Notify(&m1)
162 - r.Notify(&m2)
163 - r.Notify(&m3)
164 -
165 - var n int
166 - var wg sync.WaitGroup
167 - for i := 0; i < N; i++ {
168 - n++
169 - wg.Add(1)
170 - go func() {
171 - defer wg.Done()
172 - r.Receive(Packet{})
173 - }()
174 -
175 - if i%3 == 0 {
176 - n++
177 - wg.Add(1)
178 - go func() {
179 - defer wg.Done()
180 - r.Forward()
181 - }()
182 - }
183 - }
184 -
185 - // drain queues
186 - for i := 0; i < (n * 3); i++ {
187 - select {
188 - case <-m1.received:
189 - case <-m2.received:
190 - case <-m3.received:
191 - }
192 - }
193 -
194 - wg.Wait()
195 -
196 - // counts should be correct and all agree. and this should
197 - // run fine under `go test -race -cpu=5`
198 -
199 - t.Log("m1", m1.String())
200 - t.Log("m2", m2.String())
201 - t.Log("m3", m3.String())
202 -
203 - if m1.String() != m2.String() || m2.String() != m3.String() {
204 - t.Error("counts disagree")
205 - }
206 -}
207 -
208 -type highwatermark struct {
209 - mu sync.Mutex
210 - mark int
211 - limit int
212 - errs chan error
213 -}
214 -
215 -func (m *highwatermark) incr() {
216 - m.mu.Lock()
217 - m.mark++
218 - // fmt.Println("incr", m.mark)
219 - if m.mark > m.limit {
220 - m.errs <- fmt.Errorf("went over limit: %d/%d", m.mark, m.limit)
221 - }
222 - m.mu.Unlock()
223 -}
224 -
225 -func (m *highwatermark) decr() {
226 - m.mu.Lock()
227 - m.mark--
228 - // fmt.Println("decr", m.mark)
229 - if m.mark < 0 {
230 - m.errs <- fmt.Errorf("went under zero: %d/%d", m.mark, m.limit)
231 - }
232 - m.mu.Unlock()
233 -}
234 -
235 -func TestLimited(t *testing.T) {
236 - timeout := 10 * time.Second // huge timeout.
237 - limit := 9
238 -
239 - hwm := highwatermark{limit: limit, errs: make(chan error, 100)}
240 - n := RateLimited(limit) // will stop after 3 rounds
241 - n.Notify(1)
242 - n.Notify(2)
243 - n.Notify(3)
244 -
245 - entr := make(chan struct{})
246 - exit := make(chan struct{})
247 - done := make(chan struct{})
248 - go func() {
249 - for i := 0; i < 10; i++ {
250 - // fmt.Printf("round: %d\n", i)
251 - n.NotifyAll(func(e Notifiee) {
252 - hwm.incr()
253 - entr <- struct{}{}
254 - <-exit // wait
255 - hwm.decr()
256 - })
257 - }
258 - done <- struct{}{}
259 - }()
260 -
261 - for i := 0; i < 30; {
262 - select {
263 - case <-entr:
264 - continue // let as many enter as possible
265 - case <-time.After(1 * time.Millisecond):
266 - }
267 -
268 - // let one exit
269 - select {
270 - case <-entr:
271 - continue // in case of timing issues.
272 - case exit <- struct{}{}:
273 - case <-time.After(timeout):
274 - t.Error("got stuck")
275 - }
276 - i++
277 - }
278 -
279 - select {
280 - case <-done: // two parts done
281 - case <-time.After(timeout):
282 - t.Error("did not finish")
283 - }
284 -
285 - close(hwm.errs)
286 - for err := range hwm.errs {
287 - t.Error(err)
288 - }
289 -}