250 lines
6.4 KiB
Go
250 lines
6.4 KiB
Go
//go:build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
"nhooyr.io/websocket"
|
|
|
|
toki "git.toki-labs.com/toki/proto-socket/go"
|
|
"git.toki-labs.com/toki/proto-socket/go/packets"
|
|
)
|
|
|
|
const (
|
|
host = "127.0.0.1"
|
|
goDartWebWSPort = 29198
|
|
goDartWebWSSPort = 29199
|
|
wsPath = "/"
|
|
processTimeout = 60 * time.Second
|
|
serverObservationWindow = 200 * time.Millisecond
|
|
)
|
|
|
|
func parserMap() toki.ParserMap {
|
|
return toki.ParserMap{
|
|
toki.TypeNameOf(&packets.TestData{}): func(b []byte) (proto.Message, error) {
|
|
m := &packets.TestData{}
|
|
return m, proto.Unmarshal(b, m)
|
|
},
|
|
}
|
|
}
|
|
|
|
func browserAcceptOptions() *websocket.AcceptOptions {
|
|
return &websocket.AcceptOptions{
|
|
OriginPatterns: []string{"localhost:*", "127.0.0.1:*"},
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
fmt.Printf("INFO typeName go=%s\n", toki.TypeNameOf(&packets.TestData{}))
|
|
if err := run(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "FAIL crosstest error=%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println("PASS scenario=ws-send-push")
|
|
fmt.Println("PASS scenario=ws-request-response")
|
|
fmt.Println("PASS scenario=wss-send-push")
|
|
fmt.Println("PASS scenario=wss-request-response")
|
|
fmt.Println("PASS all go-server/dart-web-client crosstests passed")
|
|
}
|
|
|
|
func run() error {
|
|
serverTLS, err := loadWebServerTLS()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runWebSuites(serverTLS)
|
|
}
|
|
|
|
func loadWebServerTLS() (*tls.Config, error) {
|
|
dartDir, err := dartPackageDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
certFile := filepath.Join(dartDir, "test", "certs", "server.crt")
|
|
keyFile := filepath.Join(dartDir, "test", "certs", "server.key")
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
|
}
|
|
|
|
func runWebSuites(serverTLS *tls.Config) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
wsReceived := make(chan bool, 1)
|
|
wssReceived := make(chan bool, 1)
|
|
wsServer := newWebServer(goDartWebWSPort, nil, wsReceived)
|
|
wssServer := newWebServer(goDartWebWSSPort, serverTLS, wssReceived)
|
|
|
|
if err := wsServer.Start(ctx); err != nil {
|
|
return err
|
|
}
|
|
defer wsServer.Stop()
|
|
if err := wssServer.Start(ctx); err != nil {
|
|
return err
|
|
}
|
|
defer wssServer.Stop()
|
|
|
|
if err := runDartBrowserTest(); err != nil {
|
|
return err
|
|
}
|
|
if err := expectReceived("WS", wsReceived); err != nil {
|
|
return err
|
|
}
|
|
return expectReceived("WSS", wssReceived)
|
|
}
|
|
|
|
func newWebServer(port int, tlsCfg *tls.Config, received chan<- bool) *toki.WsServer {
|
|
options := toki.WsServerOptions{
|
|
AcceptOptions: browserAcceptOptions(),
|
|
}
|
|
newClient := func(conn *websocket.Conn) *toki.WsClient {
|
|
return toki.NewWsClient(conn, 0, 0, parserMap())
|
|
}
|
|
var server *toki.WsServer
|
|
if tlsCfg != nil {
|
|
server = toki.NewWsServerTLSWithOptions(host, port, wsPath, tlsCfg, options, newClient)
|
|
} else {
|
|
server = toki.NewWsServerWithOptions(host, port, wsPath, options, newClient)
|
|
}
|
|
server.OnClientConnected = func(client *toki.WsClient) {
|
|
toki.AddRequestListenerTyped[*packets.TestData, *packets.TestData](&client.Communicator, func(req *packets.TestData) (*packets.TestData, error) {
|
|
fmt.Printf("SERVER_RECEIVED index=%d message=%s\n", req.GetIndex(), req.GetMessage())
|
|
if req.GetIndex() == 101 && req.GetMessage() == "fire from dart web client" {
|
|
select {
|
|
case received <- true:
|
|
default:
|
|
}
|
|
_ = client.Send(&packets.TestData{Index: 200, Message: "push from go server"})
|
|
}
|
|
return &packets.TestData{
|
|
Index: req.GetIndex() * 2,
|
|
Message: "echo: " + req.GetMessage(),
|
|
}, nil
|
|
})
|
|
}
|
|
return server
|
|
}
|
|
|
|
func expectReceived(transport string, received <-chan bool) error {
|
|
select {
|
|
case ok := <-received:
|
|
if !ok {
|
|
return fmt.Errorf("%s web send-push server received unexpected data", transport)
|
|
}
|
|
case <-time.After(serverObservationWindow):
|
|
return fmt.Errorf("%s web send-push server did not receive expected data", transport)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runDartBrowserTest() error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), processTimeout)
|
|
defer cancel()
|
|
|
|
dartDir, err := dartPackageDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, "dart", "test", "-p", "chrome", "test/browser_ws_runtime_test.dart")
|
|
cmd.Dir = dartDir
|
|
cmd.WaitDelay = 5 * time.Second
|
|
|
|
stdoutPipe, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stderrPipe, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go pipeLines(&wg, stdoutPipe, os.Stdout)
|
|
go pipeLines(&wg, stderrPipe, os.Stderr)
|
|
|
|
waitErr := cmd.Wait()
|
|
done := make(chan struct{})
|
|
go func() { wg.Wait(); close(done) }()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(5 * time.Second):
|
|
return fmt.Errorf("dart browser test pipe scan timed out")
|
|
}
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("dart browser test timed out")
|
|
}
|
|
if waitErr != nil {
|
|
return fmt.Errorf("dart browser test failed: %w", waitErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func pipeLines(wg *sync.WaitGroup, reader io.Reader, writer *os.File) {
|
|
defer wg.Done()
|
|
scanner := bufio.NewScanner(reader)
|
|
for scanner.Scan() {
|
|
fmt.Fprintln(writer, scanner.Text())
|
|
}
|
|
}
|
|
|
|
func dartPackageDir() (string, error) {
|
|
candidates := make([]string, 0, 3)
|
|
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
if ok {
|
|
repoRoot := filepath.Dir(filepath.Dir(filepath.Dir(filename)))
|
|
candidates = append(candidates, filepath.Join(repoRoot, "dart"))
|
|
}
|
|
if wd, err := os.Getwd(); err == nil {
|
|
candidates = append(candidates, findDartPackageCandidates(wd)...)
|
|
}
|
|
if executable, err := os.Executable(); err == nil {
|
|
candidates = append(candidates, findDartPackageCandidates(filepath.Dir(executable))...)
|
|
}
|
|
|
|
for _, candidate := range candidates {
|
|
if isDartPackageDir(candidate) {
|
|
return candidate, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("cannot resolve dart package directory from candidates %v", candidates)
|
|
}
|
|
|
|
func findDartPackageCandidates(start string) []string {
|
|
candidates := make([]string, 0)
|
|
for dir := start; ; dir = filepath.Dir(dir) {
|
|
candidates = append(candidates, filepath.Join(dir, "dart"))
|
|
if filepath.Base(dir) == "dart" {
|
|
candidates = append(candidates, dir)
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
return candidates
|
|
}
|
|
}
|
|
}
|
|
|
|
func isDartPackageDir(dir string) bool {
|
|
info, err := os.Stat(filepath.Join(dir, "pubspec.yaml"))
|
|
return err == nil && !info.IsDir()
|
|
}
|