//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() }