Skip to content

Commit bd08b1a

Browse files
committed
Add ping test
1 parent e72b31d commit bd08b1a

2 files changed

Lines changed: 132 additions & 16 deletions

File tree

main.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ func init() {
3535
slog.SetDefault(handler)
3636
}
3737

38-
func startPingRoutine(ctx context.Context, wg *sync.WaitGroup, c *config.Config) {
38+
func startPingRoutine(ctx context.Context, wg *sync.WaitGroup, c *config.Config, interval time.Duration) {
3939
defer wg.Done()
4040

41-
ticker := time.NewTicker(30 * time.Second)
41+
ticker := time.NewTicker(interval)
4242
defer ticker.Stop()
4343

44-
slog.Info("Starting ping routine to GCE instance")
44+
slog.Info("Starting ping routine to GCE instance", "interval", interval)
4545

4646
for {
4747
select {
@@ -88,18 +88,13 @@ func main() {
8888
c.PowerOnCooldown = 30
8989
}
9090

91-
// Create context for graceful shutdown
9291
ctx, cancel := context.WithCancel(context.Background())
9392
defer cancel()
94-
95-
// Set up signal handling for graceful shutdown
9693
sigChan := make(chan os.Signal, 1)
9794
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
98-
99-
// Start ping routine
10095
var wg sync.WaitGroup
10196
wg.Add(1)
102-
go startPingRoutine(ctx, &wg, c)
97+
go startPingRoutine(ctx, &wg, c, 30*time.Second)
10398

10499
http.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
105100
w.WriteHeader(http.StatusOK)
@@ -127,7 +122,6 @@ func main() {
127122
p.ServeHTTP(w, r)
128123
})
129124

130-
// Start HTTP server in a goroutine
131125
server := &http.Server{Addr: ":8080"}
132126
go func() {
133127
slog.Info("Server listening on :8080")
@@ -136,22 +130,16 @@ func main() {
136130
}
137131
}()
138132

139-
// Wait for shutdown signal
140133
<-sigChan
141134
slog.Info("Received shutdown signal, gracefully shutting down...")
142-
143-
// Cancel context to stop ping routine
144135
cancel()
145136

146-
// Shutdown HTTP server
147137
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
148138
defer shutdownCancel()
149-
150139
if err := server.Shutdown(shutdownCtx); err != nil {
151140
slog.Error("Server shutdown error", "err", err)
152141
}
153142

154-
// Wait for ping routine to finish
155143
wg.Wait()
156144
slog.Info("Shutdown complete")
157145
}

main_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"net/http"
7+
"os"
8+
"strings"
9+
"sync"
10+
"testing"
11+
"time"
12+
13+
"github.com/libops/ppb/pkg/config"
14+
"github.com/libops/ppb/pkg/machine"
15+
)
16+
17+
func TestStartPingRoutine_Integration(t *testing.T) {
18+
// Track ping requests
19+
var pingCount int
20+
var pingURLs []string
21+
var mu sync.Mutex
22+
23+
mux := http.NewServeMux()
24+
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
25+
mu.Lock()
26+
defer mu.Unlock()
27+
pingCount++
28+
pingURLs = append(pingURLs, r.URL.Path)
29+
30+
w.WriteHeader(http.StatusOK)
31+
_, err := w.Write([]byte("pong"))
32+
if err != nil {
33+
slog.Error("Unable to write ping response", "err", err)
34+
os.Exit(1)
35+
}
36+
})
37+
server := &http.Server{
38+
Addr: ":8808",
39+
Handler: mux,
40+
}
41+
go func() {
42+
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
43+
// Ignore "address already in use" errors during testing
44+
if !strings.Contains(err.Error(), "address already in use") {
45+
t.Errorf("Mock server error: %v", err)
46+
}
47+
}
48+
}()
49+
50+
time.Sleep(1 * time.Second)
51+
52+
defer func() {
53+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
54+
defer cancel()
55+
err := server.Shutdown(ctx)
56+
if err != nil {
57+
slog.Error("Server shutdown failed", "err", err)
58+
os.Exit(1)
59+
}
60+
}()
61+
62+
mockMachine := machine.NewGceMachine()
63+
mockMachine.SetHostForTesting("127.0.0.1")
64+
65+
config := &config.Config{
66+
Machine: mockMachine,
67+
}
68+
69+
ctx, cancel := context.WithTimeout(context.Background(), 350*time.Millisecond)
70+
defer cancel()
71+
72+
var wg sync.WaitGroup
73+
wg.Add(1)
74+
75+
go startPingRoutine(ctx, &wg, config, 100*time.Millisecond)
76+
wg.Wait()
77+
78+
mu.Lock()
79+
defer mu.Unlock()
80+
81+
if pingCount == 0 {
82+
t.Error("Expected at least one ping request, got none")
83+
}
84+
85+
// Verify all requests were to /ping endpoint
86+
for _, url := range pingURLs {
87+
if url != "/ping" {
88+
t.Errorf("Expected ping to /ping endpoint, got %s", url)
89+
}
90+
}
91+
92+
// Should have made multiple pings (at least 2-3 in 350ms with 100ms interval)
93+
if pingCount < 2 {
94+
t.Errorf("Expected at least 2 ping requests, got %d", pingCount)
95+
}
96+
}
97+
98+
func TestStartPingRoutine_ContextCancellation(t *testing.T) {
99+
mockMachine := machine.NewGceMachine()
100+
mockMachine.SetHostForTesting("127.0.0.1")
101+
102+
config := &config.Config{
103+
Machine: mockMachine,
104+
}
105+
106+
ctx, cancel := context.WithCancel(context.Background())
107+
var wg sync.WaitGroup
108+
wg.Add(1)
109+
110+
routineFinished := make(chan bool, 1)
111+
112+
go func() {
113+
startPingRoutine(ctx, &wg, config, 50*time.Millisecond)
114+
routineFinished <- true
115+
}()
116+
117+
time.Sleep(100 * time.Millisecond)
118+
cancel()
119+
120+
wg.Wait()
121+
122+
// Verify the routine actually finished
123+
select {
124+
case <-routineFinished:
125+
case <-time.After(1 * time.Second):
126+
t.Error("Ping routine did not finish within expected time after context cancellation")
127+
}
128+
}

0 commit comments

Comments
 (0)