proto-socket/go/crosstest/go_dart_web.go
toki 5df325bf96 feat: Dart Web E2E 테스트 및 크로스 테스트 추가
- Dart Web 브라우저 E2E 테스트 프레임워크 추가
- browser_ws_dart_io_test, browser_ws_kotlin_test 등 브라우저 통합 테스트 추가
- Dart Web 크로스테스트 케이스 추가 (go, kotlin, python, typescript)
- TypeScript 브라우저 웹소켓 클라이언트 개선
- Node.js 웹소켓 서버 클라이언트 업데이트
- 테스트 실행 매트릭스 스킬 및 스크립트 업데이트
- README.md 문서 업데이트
2026-05-25 00:30:37 +09:00

202 lines
5.1 KiB
Go

//go:build ignore
package main
import (
"bufio"
"context"
"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
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=send-push")
fmt.Println("PASS scenario=request-response")
fmt.Println("PASS all go-server/dart-web-client crosstests passed")
}
func run() error {
return runWebWSSuite()
}
func runWebWSSuite() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
received := make(chan bool, 1)
server := toki.NewWsServerWithOptions(host, goDartWebWSPort, wsPath, toki.WsServerOptions{
AcceptOptions: browserAcceptOptions(),
}, func(conn *websocket.Conn) *toki.WsClient {
return toki.NewWsClient(conn, 0, 0, parserMap())
})
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
})
}
if err := server.Start(ctx); err != nil {
return err
}
defer server.Stop()
if err := runDartBrowserTest(); err != nil {
return err
}
select {
case ok := <-received:
if !ok {
return fmt.Errorf("WS web send-push server received unexpected data")
}
case <-time.After(serverObservationWindow):
return fmt.Errorf("WS web send-push server did not receive expected data")
}
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()
}