Skip to content

Commit c91a322

Browse files
committed
Tests added and error handling improved
1 parent 69dfe63 commit c91a322

14 files changed

Lines changed: 832 additions & 102 deletions

.github/workflows/build.yml

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
11
name: Build & Test
22

3-
on:
4-
push:
5-
branches: [ main ]
6-
pull_request:
7-
branches: [ main ]
3+
on: [
4+
push,
5+
pull_request
6+
]
87

98
jobs:
109
build:
10+
runs-on: ubuntu-latest
11+
12+
services:
13+
etcd:
14+
image: gcr.io/etcd-development/etcd:v3.6.5
15+
ports:
16+
- 2379:2379
17+
env:
18+
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
19+
ETCD_ADVERTISE_CLIENT_URLS: http://localhost:2379
20+
1121
strategy:
1222
matrix:
13-
os: [
14-
windows-latest,
15-
ubuntu-latest,
16-
macos-latest
17-
]
1823
go: [
19-
'^1.18',
20-
'^1.19',
21-
'^1.20',
22-
'^1.21',
23-
'^1.22',
24-
'^1.23',
25-
'^1.24',
26-
'^1.25'
24+
'1.24',
25+
'1.25'
2726
]
2827

29-
runs-on: ${{ matrix.os }}
30-
3128
steps:
32-
- uses: actions/checkout@v4
33-
- uses: actions/setup-go@v5
29+
- uses: actions/checkout@v5
30+
- uses: actions/setup-go@v6
3431
with:
35-
go-version: ${{ matrix.go }}
36-
37-
- name: Build
38-
run: go build -v ./...
39-
40-
- name: Test
41-
run: go test -v ./...
32+
go-version: ~${{matrix.go}}
33+
- run: go build ./...
34+
- run: go test ./...
35+
env:
36+
ETCD_ADDR: localhost:2379
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 RPCPlatform Authors
2+
* Copyright 2025 RPCPlatform Authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,16 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
package gears
17+
package rpcplatform
1818

19-
func FixPath(path string) string {
20-
if path[0] != '/' {
21-
path = "/" + path
22-
}
19+
import (
20+
"errors"
21+
)
2322

24-
if path[len(path)-1] == '/' {
25-
path = path[:len(path)-1]
26-
}
27-
28-
return path
29-
}
23+
var (
24+
ErrInvalidEtcdPrefix = errors.New("invalid etcd prefix")
25+
ErrInvalidTargetName = errors.New("invalid target name")
26+
ErrInvalidServerName = errors.New("invalid server name")
27+
)

internal/balancer/picker/picker.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ package picker
1919
import (
2020
"cmp"
2121
"math"
22+
"math/rand/v2"
2223
"slices"
2324
"sync"
2425

2526
"github.com/nexcode/rpcplatform/internal/config"
26-
"github.com/nexcode/rpcplatform/internal/gears"
2727
"github.com/nexcode/rpcplatform/internal/grpcattrs"
2828
"google.golang.org/grpc/balancer"
2929
"google.golang.org/grpc/balancer/base"
@@ -36,16 +36,22 @@ func New(childStates []endpointsharding.ChildState, config *config.Client) balan
3636
return base.NewErrPicker(errNoServerAvailableForPick)
3737
}
3838

39+
var connecting bool
3940
var totalWeight int
41+
4042
pickerStates := make([]*state, 0, len(childStates))
4143

4244
for _, childState := range childStates {
43-
if childState.State.ConnectivityState != connectivity.Ready {
45+
attributes := grpcattrs.GetAttributes(childState.Endpoint.Attributes)
46+
if attributes.BalancerWeight <= 0 {
4447
continue
4548
}
4649

47-
attributes := grpcattrs.GetAttributes(childState.Endpoint.Attributes)
48-
if attributes.BalancerWeight <= 0 {
50+
if childState.State.ConnectivityState == connectivity.Connecting {
51+
connecting = true
52+
}
53+
54+
if childState.State.ConnectivityState != connectivity.Ready {
4955
continue
5056
}
5157

@@ -57,6 +63,10 @@ func New(childStates []endpointsharding.ChildState, config *config.Client) balan
5763
}
5864

5965
if len(pickerStates) == 0 {
66+
if connecting {
67+
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
68+
}
69+
6070
return base.NewErrPicker(errNoServerAvailableForPick)
6171
}
6272

@@ -99,7 +109,7 @@ func New(childStates []endpointsharding.ChildState, config *config.Client) balan
99109
}
100110
}
101111

102-
picker.next = gears.Intn(totalWeight)
112+
picker.next = rand.IntN(totalWeight)
103113
return picker
104114
}
105115

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2025 RPCPlatform Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package picker
18+
19+
import (
20+
"slices"
21+
"testing"
22+
23+
"github.com/nexcode/rpcplatform/internal/attributes"
24+
"github.com/nexcode/rpcplatform/internal/config"
25+
"github.com/nexcode/rpcplatform/internal/grpcattrs"
26+
"google.golang.org/grpc/balancer"
27+
"google.golang.org/grpc/balancer/endpointsharding"
28+
"google.golang.org/grpc/connectivity"
29+
"google.golang.org/grpc/resolver"
30+
)
31+
32+
func TestNew(t *testing.T) {
33+
t.Parallel()
34+
35+
childStates := []endpointsharding.ChildState{{
36+
State: balancer.State{
37+
ConnectivityState: connectivity.Ready,
38+
Picker: &namedPicker{name: 1},
39+
},
40+
Endpoint: resolver.Endpoint{
41+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
42+
BalancerWeight: 1,
43+
BalancerPriority: 1,
44+
}),
45+
},
46+
}, {
47+
State: balancer.State{
48+
ConnectivityState: connectivity.Ready,
49+
Picker: &namedPicker{name: 2},
50+
},
51+
Endpoint: resolver.Endpoint{
52+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
53+
BalancerWeight: 7,
54+
BalancerPriority: 2,
55+
}),
56+
},
57+
}, {
58+
State: balancer.State{
59+
ConnectivityState: connectivity.Ready,
60+
Picker: &namedPicker{name: 3},
61+
},
62+
Endpoint: resolver.Endpoint{
63+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
64+
BalancerWeight: 5,
65+
BalancerPriority: 2,
66+
}),
67+
},
68+
}, {
69+
State: balancer.State{
70+
ConnectivityState: connectivity.Ready,
71+
Picker: &namedPicker{name: 4},
72+
},
73+
Endpoint: resolver.Endpoint{
74+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
75+
BalancerWeight: 3,
76+
BalancerPriority: 2,
77+
}),
78+
},
79+
}, {
80+
State: balancer.State{
81+
ConnectivityState: connectivity.Ready,
82+
Picker: &namedPicker{name: 5},
83+
},
84+
Endpoint: resolver.Endpoint{
85+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
86+
BalancerWeight: 0,
87+
BalancerPriority: 3,
88+
}),
89+
},
90+
}, {
91+
State: balancer.State{
92+
ConnectivityState: connectivity.TransientFailure,
93+
Picker: &namedPicker{name: 6},
94+
},
95+
Endpoint: resolver.Endpoint{
96+
Attributes: grpcattrs.SetAttributes(nil, &attributes.Attributes{
97+
BalancerWeight: 1,
98+
BalancerPriority: 3,
99+
}),
100+
},
101+
}}
102+
103+
config := &config.Client{
104+
MaxActiveServers: 3,
105+
}
106+
107+
picker := New(childStates, config).(*picker)
108+
109+
actualSequence := make([]int, len(picker.pickers))
110+
for i, picker := range picker.pickers {
111+
actualSequence[i] = picker.(*namedPicker).name
112+
}
113+
114+
expectedSequence := []int{2, 3, 4, 2, 3, 2, 2, 3, 4, 2, 3, 2, 2, 3, 4}
115+
116+
if !slices.Equal(actualSequence, expectedSequence) {
117+
t.Errorf("picker sequence = %v, want: %v", actualSequence, expectedSequence)
118+
}
119+
}
120+
121+
type namedPicker struct {
122+
name int
123+
}
124+
125+
func (namedPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
126+
return balancer.PickResult{}, nil
127+
}

internal/gears/gears_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2025 RPCPlatform Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package gears
18+
19+
import (
20+
"sync"
21+
"testing"
22+
)
23+
24+
func TestUID(t *testing.T) {
25+
t.Parallel()
26+
27+
goroutines := 10
28+
generations := 100
29+
30+
var wg1, wg2 sync.WaitGroup
31+
idChan := make(chan string, goroutines*generations)
32+
idMap := make(map[string]struct{})
33+
34+
wg1.Add(goroutines)
35+
wg2.Add(1)
36+
37+
go func() {
38+
defer wg2.Done()
39+
40+
for id := range idChan {
41+
idMap[id] = struct{}{}
42+
}
43+
}()
44+
45+
for range goroutines {
46+
go func() {
47+
defer wg1.Done()
48+
49+
for range generations {
50+
idChan <- UID()
51+
}
52+
}()
53+
}
54+
55+
wg1.Wait()
56+
close(idChan)
57+
wg2.Wait()
58+
59+
if length := len(idMap); length != goroutines*generations {
60+
t.Errorf("there are %v unique IDs, want: %v", length, goroutines*generations)
61+
}
62+
}

internal/gears/intn.go

Lines changed: 0 additions & 21 deletions
This file was deleted.

internal/gears/var.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@
1616

1717
package gears
1818

19-
import (
20-
"math/rand"
21-
"time"
22-
)
23-
2419
var (
25-
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
2620
unique = randBytes()
2721
counter = randUint32()
2822
)

0 commit comments

Comments
 (0)