Skip to content

Commit 550b40e

Browse files
Merge pull request #16 from fabricekabongo/codex/increase-code-coverage-for-edge-cases
2 parents 7a98498 + e08e047 commit 550b40e

2 files changed

Lines changed: 157 additions & 0 deletions

File tree

clustering/cluster_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package clustering
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/fabricekabongo/loggerhead/config"
8+
"github.com/hashicorp/memberlist"
9+
)
10+
11+
func TestStateToString(t *testing.T) {
12+
cases := map[memberlist.NodeStateType]string{
13+
memberlist.StateAlive: "Alive",
14+
memberlist.StateSuspect: "Suspect",
15+
memberlist.StateLeft: "Left",
16+
memberlist.StateDead: "Dead",
17+
memberlist.NodeStateType(255): "Unknown",
18+
}
19+
20+
for state, expected := range cases {
21+
if got := StateToString(state); got != expected {
22+
t.Fatalf("expected %s for state %v but got %s", expected, state, got)
23+
}
24+
}
25+
}
26+
27+
func TestGetClusterIPsWithSeedNode(t *testing.T) {
28+
cfg := config.Config{SeedNode: "10.0.0.1"}
29+
30+
ips, err := getClusterIPs(cfg)
31+
if err != nil {
32+
t.Fatalf("unexpected error: %v", err)
33+
}
34+
35+
if len(ips) != 1 || ips[0] != cfg.SeedNode {
36+
t.Fatalf("expected seed node slice with %s, got %#v", cfg.SeedNode, ips)
37+
}
38+
}
39+
40+
func TestGetClusterIPsWithDNS(t *testing.T) {
41+
cfg := config.Config{ClusterDNS: "localhost"}
42+
43+
ips, err := getClusterIPs(cfg)
44+
if err != nil {
45+
t.Fatalf("unexpected error: %v", err)
46+
}
47+
48+
if len(ips) == 0 {
49+
t.Fatal("expected at least one IP from localhost resolution")
50+
}
51+
}
52+
53+
func TestGetClusterIPsDNSFailure(t *testing.T) {
54+
cfg := config.Config{ClusterDNS: "nonexistent.invalid."}
55+
56+
_, err := getClusterIPs(cfg)
57+
if !errors.Is(err, ErrFailedToExtractIPsFromDNS) {
58+
t.Fatalf("expected ErrFailedToExtractIPsFromDNS, got %v", err)
59+
}
60+
}
61+
62+
func TestGetIPsFromDomainNameInvalid(t *testing.T) {
63+
_, err := getIPsFromDomainName("nonexistent.invalid.")
64+
if err == nil {
65+
t.Fatal("expected lookup error for invalid domain")
66+
}
67+
}

clustering/engine_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package clustering
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/fabricekabongo/loggerhead/query"
9+
"github.com/fabricekabongo/loggerhead/world"
10+
"github.com/hashicorp/memberlist"
11+
)
12+
13+
func TestEngineDecoratorQueuesBroadcasts(t *testing.T) {
14+
ctx, cancel := context.WithCancel(context.Background())
15+
defer cancel()
16+
17+
cluster := &Cluster{broadcasts: &memberlist.TransmitLimitedQueue{NumNodes: func() int { return 1 }, RetransmitMult: 1}}
18+
engine := query.NewWriteQueryEngine(world.NewWorld())
19+
20+
decorator := NewEngineDecorator(ctx, cluster, engine)
21+
ed := decorator.(*EngineDecorator)
22+
23+
result := ed.ExecuteQuery("SAVE ns loc 1 1")
24+
if result != "1.0,saved\n" {
25+
t.Fatalf("expected save confirmation, got %q", result)
26+
}
27+
28+
var broadcasts [][]byte
29+
deadline := time.Now().Add(200 * time.Millisecond)
30+
for time.Now().Before(deadline) {
31+
broadcasts = cluster.broadcasts.GetBroadcasts(0, 1024)
32+
if len(broadcasts) > 0 {
33+
break
34+
}
35+
time.Sleep(10 * time.Millisecond)
36+
}
37+
38+
if len(broadcasts) != 1 {
39+
t.Fatalf("expected 1 broadcast, got %d", len(broadcasts))
40+
}
41+
42+
if string(broadcasts[0]) != "SAVE ns loc 1 1" {
43+
t.Fatalf("unexpected broadcast message %q", string(broadcasts[0]))
44+
}
45+
}
46+
47+
func TestEngineDecoratorStopsWithCanceledContext(t *testing.T) {
48+
ctx, cancel := context.WithCancel(context.Background())
49+
cluster := &Cluster{broadcasts: &memberlist.TransmitLimitedQueue{NumNodes: func() int { return 1 }, RetransmitMult: 1}}
50+
engine := query.NewWriteQueryEngine(world.NewWorld())
51+
52+
NewEngineDecorator(ctx, cluster, engine)
53+
cancel()
54+
55+
time.Sleep(10 * time.Millisecond)
56+
57+
select {
58+
case <-ctx.Done():
59+
default:
60+
t.Fatal("context should be cancelled")
61+
}
62+
}
63+
64+
func TestLocationBroadcastBehaviors(t *testing.T) {
65+
first := NewLocationBroadcast("SAVE ns loc 1 1")
66+
second := NewLocationBroadcast("SAVE ns loc 1 1")
67+
different := NewLocationBroadcast("SAVE ns loc 2 2")
68+
69+
if !first.Invalidates(second) {
70+
t.Fatal("expected identical commands to invalidate each other")
71+
}
72+
73+
if different.Invalidates(first) {
74+
t.Fatal("expected different commands to not invalidate")
75+
}
76+
77+
notify := make(chan struct{})
78+
withNotify := &LocationBroadcast{command: "SAVE ns loc 1 1", msg: []byte("payload"), notify: notify}
79+
withNotify.Finished()
80+
81+
select {
82+
case <-notify:
83+
case <-time.After(time.Second):
84+
t.Fatal("expected notify channel to be closed")
85+
}
86+
87+
if string(first.Message()) != "SAVE ns loc 1 1" {
88+
t.Fatalf("unexpected message payload %q", string(first.Message()))
89+
}
90+
}

0 commit comments

Comments
 (0)