sshpiper/e2e/main_test.go
2025-08-30 09:59:29 -07:00

184 lines
3.9 KiB
Go

// run with docker-compose up --build --abort-on-container-exit
package e2e_test
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"strconv"
"strings"
"testing"
"time"
"github.com/creack/pty"
)
const testprivatekey = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDURkx99uaw1KddraZcLpB5kfMrWwvUz2fPOoArLcpz9QAAAJC+j0+Svo9P
kgAAAAtzc2gtZWQyNTUxOQAAACDURkx99uaw1KddraZcLpB5kfMrWwvUz2fPOoArLcpz9Q
AAAEDcQgdh2z2r/6blq0ziJ1l6s6IAX8C+9QHfAH931cHNO9RGTH325rDUp12tplwukHmR
8ytbC9TPZ886gCstynP1AAAADWJvbGlhbkB1YnVudHU=
-----END OPENSSH PRIVATE KEY-----
`
const testpublickey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1`
const waitTimeout = time.Second * 10
func waitForEndpointReady(addr string) {
waitForEndpointReadyWithTimeout(addr, waitTimeout)
}
func waitForEndpointReadyWithTimeout(addr string, timeout time.Duration) {
now := time.Now()
timeout = max(timeout, waitTimeout)
for {
if time.Since(now) > timeout {
log.Panic("timeout waiting for endpoint " + addr)
}
conn, err := net.Dial("tcp", addr)
if err == nil {
log.Printf("endpoint %s is ready", addr)
conn.Close()
break
}
time.Sleep(time.Second)
}
}
func runCmd(cmd string, args ...string) (*exec.Cmd, io.Writer, io.Reader, error) {
newargs := append([]string{cmd}, args...)
newargs = append([]string{"-i0", "-o0", "-e0"}, newargs...)
c := exec.Command("stdbuf", newargs...)
c.SysProcAttr = sigtermForPdeathsig
f, err := pty.Start(c)
if err != nil {
return nil, nil, nil, err
}
var buf bytes.Buffer
r := io.TeeReader(f, &buf)
go func() {
_, _ = io.Copy(os.Stdout, r)
}()
log.Printf("starting %v", c.Args)
return c, f, &buf, nil
}
func runCmdAndWait(cmd string, args ...string) error {
c, _, _, err := runCmd(cmd, args...)
if err != nil {
return err
}
return c.Wait()
}
func waitForStdoutContains(stdout io.Reader, text string, cb func(string)) {
st := time.Now()
for {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, text) {
cb(line)
return
}
}
if time.Since(st) > waitTimeout {
log.Panicf("timeout waiting for [%s] from prompt", text)
return
}
time.Sleep(time.Second) // stdout has no data yet
}
}
func enterPassword(stdin io.Writer, stdout io.Reader, password string) {
waitForStdoutContains(stdout, "'s password", func(_ string) {
_, _ = fmt.Fprintf(stdin, "%v\n", password)
log.Printf("got password prompt, sending password")
})
}
func checkSharedFileContent(t *testing.T, targetfie string, expected string) {
f, err := os.Open(fmt.Sprintf("/shared/%v", targetfie))
if err != nil {
t.Errorf("failed to open shared file, %v", err)
}
defer f.Close()
b, err := io.ReadAll(f)
if err != nil {
t.Errorf("failed to read shared file, %v", err)
}
if string(b) != expected {
t.Errorf("shared file content mismatch, expected %v, got %v", expected, string(b))
}
}
func killCmd(c *exec.Cmd) {
if c.Process != nil {
if err := c.Process.Kill(); err != nil {
log.Printf("failed to kill ssh process, %v", err)
}
}
}
func runAndGetStdout(cmd string, args ...string) ([]byte, error) {
c, _, stdout, err := runCmd(cmd, args...)
if err != nil {
return nil, err
}
if err := c.Wait(); err != nil {
return nil, err
}
return io.ReadAll(stdout)
}
func nextAvaliablePort() int {
l, err := net.Listen("tcp", ":0")
if err != nil {
log.Panic(err)
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port
}
func nextAvailablePiperAddress() (string, string) {
port := strconv.Itoa(nextAvaliablePort())
return net.JoinHostPort("127.0.0.1", (port)), port
}
func TestMain(m *testing.M) {
if os.Getenv("SSHPIPERD_E2E_TEST") != "1" {
log.Printf("skipping e2e test")
os.Exit(0)
return
}
_ = runCmdAndWait("ssh", "-V")
for _, ep := range []string{
"host-password:2222",
"host-publickey:2222",
} {
waitForEndpointReady(ep)
}
os.Exit(m.Run())
}