Skip to content

Commit 02d481b

Browse files
authored
test: Add integration tests for run() listener spawning (#81)
Tests that actually spawn real listeners via run(), connect via SSH, and verify graceful shutdown via context cancellation. Covers: - Platform listener path (GenericListener) - Scenario listener path (ScenarioListener) - goroutine loop, wg.Wait(), return nil Removes coverage-ignore from the goroutine loop in run(). Closes #80
1 parent 9419d11 commit 02d481b

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

cissh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func run(ctx context.Context, cli utils.CLI) error {
8989
}
9090

9191
var wg sync.WaitGroup
92-
for _, config := range configs { // coverage-ignore
92+
for _, config := range configs {
9393
wg.Add(1)
9494
go func(cfg listenerConfig) {
9595
defer wg.Done()

cissh_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ package main
22

33
import (
44
"context"
5+
"fmt"
6+
"net"
57
"os"
68
"path/filepath"
79
"testing"
10+
"time"
11+
12+
gossh "golang.org/x/crypto/ssh"
813

914
"github.com/tbotnz/cisshgo/utils"
1015
)
@@ -30,6 +35,123 @@ platforms:
3035
return tmpFile
3136
}
3237

38+
func freePort(t *testing.T) int {
39+
t.Helper()
40+
ln, err := net.Listen("tcp", "127.0.0.1:0")
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
port := ln.Addr().(*net.TCPAddr).Port
45+
ln.Close()
46+
return port
47+
}
48+
49+
func waitReady(t *testing.T, addr string) {
50+
t.Helper()
51+
for i := 0; i < 20; i++ {
52+
conn, err := net.DialTimeout("tcp", addr, 100*time.Millisecond)
53+
if err == nil {
54+
conn.Close()
55+
return
56+
}
57+
time.Sleep(50 * time.Millisecond)
58+
}
59+
t.Fatalf("server at %s never became ready", addr)
60+
}
61+
62+
func sshDial(t *testing.T, addr string) {
63+
t.Helper()
64+
cfg := &gossh.ClientConfig{
65+
User: "admin",
66+
Auth: []gossh.AuthMethod{gossh.Password("admin")},
67+
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
68+
Timeout: 2 * time.Second,
69+
}
70+
client, err := gossh.Dial("tcp", addr, cfg)
71+
if err != nil {
72+
t.Fatalf("ssh dial: %v", err)
73+
}
74+
client.Close()
75+
}
76+
77+
func TestRun_PlatformListener(t *testing.T) {
78+
port := freePort(t)
79+
addr := net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port))
80+
81+
ctx, cancel := context.WithCancel(context.Background())
82+
defer cancel()
83+
84+
cli := utils.CLI{Listeners: 1, StartingPort: port, Platform: "csr1000v", TranscriptMap: validTranscriptMap(t)}
85+
done := make(chan error, 1)
86+
go func() { done <- run(ctx, cli) }()
87+
88+
waitReady(t, addr)
89+
sshDial(t, addr)
90+
91+
cancel()
92+
if err := <-done; err != nil {
93+
t.Errorf("run() returned error: %v", err)
94+
}
95+
}
96+
97+
func TestRun_ScenarioListener(t *testing.T) {
98+
dir := t.TempDir()
99+
100+
// Write transcript files
101+
transcriptFile := filepath.Join(dir, "show_version.txt")
102+
os.WriteFile(transcriptFile, []byte("version output\n"), 0644)
103+
seqFile := filepath.Join(dir, "seq_step.txt")
104+
os.WriteFile(seqFile, []byte("seq output\n"), 0644)
105+
106+
tmContent := `---
107+
platforms:
108+
csr1000v:
109+
vendor: "cisco"
110+
hostname: "testhost"
111+
password: "admin"
112+
command_transcripts:
113+
"show version": "show_version.txt"
114+
context_search:
115+
base: ">"
116+
context_hierarchy:
117+
">": "exit"
118+
scenarios:
119+
test-scenario:
120+
platform: csr1000v
121+
sequence:
122+
- command: "show running-config"
123+
transcript: "seq_step.txt"
124+
`
125+
tmFile := filepath.Join(dir, "transcript_map.yaml")
126+
os.WriteFile(tmFile, []byte(tmContent), 0644)
127+
128+
invContent := `---
129+
devices:
130+
- scenario: test-scenario
131+
count: 1
132+
`
133+
invFile := filepath.Join(dir, "inventory.yaml")
134+
os.WriteFile(invFile, []byte(invContent), 0644)
135+
136+
port := freePort(t)
137+
addr := net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port))
138+
139+
ctx, cancel := context.WithCancel(context.Background())
140+
defer cancel()
141+
142+
cli := utils.CLI{StartingPort: port, Platform: "csr1000v", TranscriptMap: tmFile, Inventory: invFile}
143+
done := make(chan error, 1)
144+
go func() { done <- run(ctx, cli) }()
145+
146+
waitReady(t, addr)
147+
sshDial(t, addr)
148+
149+
cancel()
150+
if err := <-done; err != nil {
151+
t.Errorf("run() returned error: %v", err)
152+
}
153+
}
154+
33155
func TestRun_ZeroListeners(t *testing.T) {
34156
cli := utils.CLI{Listeners: 0, StartingPort: 10000, Platform: "csr1000v", TranscriptMap: validTranscriptMap(t)}
35157
if err := run(context.Background(), cli); err != nil {

0 commit comments

Comments
 (0)