feat: dart web crosstest and related test infrastructure updates
This commit is contained in:
parent
c94f2e82b5
commit
617ec02090
19 changed files with 440 additions and 129 deletions
16
README.md
16
README.md
|
|
@ -210,7 +210,7 @@ func main() {
|
|||
Go 구현체는 다음 표면도 제공한다.
|
||||
|
||||
- TCP: `NewTcpServer`, `DialTcp`, `NewTcpServerTLS`, `DialTcpTLS`
|
||||
- WebSocket: `NewWsServer`, `DialWs`, `NewWsServerTLS`, `DialWss`
|
||||
- WebSocket: `NewWsServer`, `NewWsServerWithOptions`, `DialWs`, `NewWsServerTLS`, `NewWsServerTLSWithOptions`, `DialWss`
|
||||
- Shared helpers: `Send`, `SendRequest`, `AddListenerTyped`, `AddRequestListenerTyped`, `Broadcast`
|
||||
|
||||
다른 저장소에서 이 모듈을 사용하는 복사 가능한 scaffold가 필요하면 [examples/go-module-consumer/](examples/go-module-consumer/)를 본다.
|
||||
|
|
@ -309,17 +309,17 @@ cd typescript
|
|||
|
||||
### Dart.io와 Dart.web E2E 매트릭스
|
||||
|
||||
위 표의 `Dart` 열은 Dart VM/IO client를 뜻한다. `Dart.web`은 별도 축이다. 브라우저 WS/WSS client만 가능하며, 브라우저에서는 TCP와 server 역할이 불가능하므로 해당 cell은 `N/A`다. Dart.web WSS는 self-signed test certificate로 자동화할 수 없는 항목이 아니다. 현재 남은 coverage gap은 브라우저가 테스트용 self-signed CA/certificate를 신뢰하도록 만드는 자동화와 그에 따른 Dart.web WSS 매트릭스 편입이다.
|
||||
위 표의 `Dart` 열은 Dart VM/IO client를 뜻한다. `Dart.web`은 별도 축이다. 브라우저 WS/WSS client만 가능하며, 브라우저에서는 TCP와 server 역할이 불가능하므로 해당 cell은 `N/A`다. Dart.web WSS는 테스트용 self-signed certificate의 SPKI를 허용하는 Chrome 실행 wrapper로 local/remote 브라우저 검증에 포함한다.
|
||||
|
||||
| Server | Dart.io TCP | Dart.io WS | Dart.io TLS TCP | Dart.io WSS | Dart.web WS | Dart.web WSS | Dart.web TCP |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Dart.io | Covered | Covered | Covered | Covered | Covered | Required gap | N/A |
|
||||
| Go | Covered | Covered | Covered | Covered | Covered | Required gap | N/A |
|
||||
| Kotlin | Covered | Covered | Covered | Covered | Covered | Required gap | N/A |
|
||||
| Python | Covered | Covered | Covered | Covered | Covered | Required gap | N/A |
|
||||
| TypeScript | Covered | Covered | Covered | Covered | Covered | Required gap | N/A |
|
||||
| Dart.io | Covered | Covered | Covered | Covered | Covered | Covered | N/A |
|
||||
| Go | Covered | Covered | Covered | Covered | Covered | Covered | N/A |
|
||||
| Kotlin | Covered | Covered | Covered | Covered | Covered | Covered | N/A |
|
||||
| Python | Covered | Covered | Covered | Covered | Covered | Covered | N/A |
|
||||
| TypeScript | Covered | Covered | Covered | Covered | Covered | Covered | N/A |
|
||||
|
||||
각 Dart.web WS cell은 server-language runner가 해당 WS server를 띄우고, matching browser test file에 대해 `dart test -p chrome`을 실행하며, send-push와 request-response 시나리오를 검증한다. Dart.web WSS cell도 같은 시나리오를 검증해야 하며, 테스트용 self-signed CA/certificate trust 자동화가 추가되기 전까지는 완전 coverage의 남은 항목으로 본다.
|
||||
각 Dart.web cell은 server-language runner가 WS와 WSS server를 함께 띄우고, matching browser test file에 대해 `dart test -p chrome`을 실행하며, WS send-push/request-response와 WSS send-push/request-response 4개 시나리오를 검증한다.
|
||||
|
||||
```bash
|
||||
cd dart && dart run crosstest/dart_web.dart
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ VERSIONING.md — 프로토콜/패키지 버전 정책
|
|||
- 기본 검증 명령은 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all`이다.
|
||||
- 테스트 coverage 평가는 라인/브랜치 수치가 아니라 프로토콜 계약 시나리오가 매트릭스에서 빠짐없이 검증되는지로 판단한다.
|
||||
- Dart.web remote browser host 사용은 의도된 local 테스트 환경이다. 환경을 이전할 때는 그대로 유지할 규칙이 아니라 새 local 환경 규칙으로 재정의해야 한다.
|
||||
- Dart.web WSS는 self-signed certificate로 자동화할 수 없는 항목이 아니다. 현재 남은 coverage gap은 브라우저가 테스트용 self-signed CA/certificate를 신뢰하도록 만드는 자동화와 그에 따른 Dart.web WSS 매트릭스 편입이다.
|
||||
- Dart.web WSS는 테스트용 self-signed certificate의 SPKI를 허용하는 Chrome 실행 wrapper로 local/remote 브라우저 매트릭스에 포함한다.
|
||||
- CI/CD runner 연결은 현재 설계상 과하며, 사용자가 명시적으로 요청하지 않으면 제안하거나 요구하지 않는다.
|
||||
|
||||
## 마일스톤 컨텍스트 로딩
|
||||
|
|
|
|||
|
|
@ -132,19 +132,23 @@ tools/check_proto_sync.sh
|
|||
|---|---|---|
|
||||
| Dart | `dart pub get && dart test && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js` | PASS |
|
||||
|
||||
**언어간 통신**
|
||||
| 서버 \ 클라이언트 | Dart | Go | Kotlin | Python | TypeScript |
|
||||
|---|---|---|---|---|---|
|
||||
| Dart | PASS | PASS | PASS | PASS | PASS |
|
||||
**언어 PASS 매트릭스**
|
||||
| 서버 \ 클라이언트 | Dart.io | Dart.web | Go | Kotlin | Python | TypeScript |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Dart.io | PASS | PASS | PASS | PASS | PASS | PASS |
|
||||
| Go | PASS | PASS | PASS | PASS | PASS | PASS |
|
||||
| Kotlin | PASS | PASS | PASS | PASS | PASS | PASS |
|
||||
| Python | PASS | PASS | PASS | PASS | PASS | PASS |
|
||||
| TypeScript | PASS | PASS | PASS | PASS | PASS | PASS |
|
||||
|
||||
**Dart.web 클라이언트 통신**
|
||||
| 서버 | Dart.web (WS) | PASS scenarios | Expected | FAIL lines |
|
||||
**크로스테스트 상세**
|
||||
| 방향 | 결과 | PASS scenarios | Expected | FAIL lines |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Dart.io | PASS | 2 | 2 | 0 |
|
||||
| Go | PASS | 2 | 2 | 0 |
|
||||
| Kotlin | PASS | 2 | 2 | 0 |
|
||||
| Python | PASS | 2 | 2 | 0 |
|
||||
| TypeScript | PASS | 2 | 2 | 0 |
|
||||
| Dart.io -> Dart.web | PASS | 4 | 4 | 0 |
|
||||
| Go -> Dart.web | PASS | 4 | 4 | 0 |
|
||||
| Kotlin -> Dart.web | PASS | 4 | 4 | 0 |
|
||||
| Python -> Dart.web | PASS | 4 | 4 | 0 |
|
||||
| TypeScript -> Dart.web | PASS | 4 | 4 | 0 |
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ declare -A web_fail_lines
|
|||
declare -A web_cmds
|
||||
declare -A web_block_reason
|
||||
|
||||
expected_web_pass_lines=2
|
||||
expected_web_pass_lines=4
|
||||
|
||||
count_lines() {
|
||||
local pattern="$1"
|
||||
|
|
@ -157,7 +157,11 @@ chrome_available() {
|
|||
|
||||
remote_chrome_export_command() {
|
||||
cat <<'REMOTE_CHROME'
|
||||
if [ -x "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ]; then
|
||||
if [ -n "${CHROME_EXECUTABLE:-}" ] && [ -x "$CHROME_EXECUTABLE" ]; then
|
||||
:
|
||||
elif [ -n "${repo_dir:-}" ] && [ -x "$repo_dir/.agent-test/chrome-proto-socket-wss" ]; then
|
||||
export CHROME_EXECUTABLE="$repo_dir/.agent-test/chrome-proto-socket-wss"
|
||||
elif [ -x "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ]; then
|
||||
export CHROME_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
elif command -v google-chrome >/dev/null 2>&1; then
|
||||
export CHROME_EXECUTABLE="$(command -v google-chrome)"
|
||||
|
|
@ -172,8 +176,28 @@ fi
|
|||
REMOTE_CHROME
|
||||
}
|
||||
|
||||
remote_chrome_wrapper_setup_command() {
|
||||
cat <<'REMOTE_CHROME_WRAPPER'
|
||||
if [ -x "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ] &&
|
||||
command -v openssl >/dev/null 2>&1 &&
|
||||
[ -f dart/test/certs/server.crt ]; then
|
||||
mkdir -p "$repo_dir/.agent-test"
|
||||
spki="$(openssl x509 -in dart/test/certs/server.crt -pubkey -noout |
|
||||
openssl pkey -pubin -outform DER |
|
||||
openssl dgst -sha256 -binary |
|
||||
openssl base64)"
|
||||
cat > "$repo_dir/.agent-test/chrome-proto-socket-wss" <<WRAPPER
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
exec "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --ignore-certificate-errors-spki-list="$spki" --allow-insecure-localhost "\$@"
|
||||
WRAPPER
|
||||
chmod +x "$repo_dir/.agent-test/chrome-proto-socket-wss"
|
||||
fi
|
||||
REMOTE_CHROME_WRAPPER
|
||||
}
|
||||
|
||||
prepare_remote_web_repo() {
|
||||
local repo_name_q repo_url_q repo_ref_q chrome_cmd
|
||||
local repo_name_q repo_url_q repo_ref_q chrome_cmd chrome_wrapper_cmd
|
||||
|
||||
if [ -z "$remote_web_repo_url" ]; then
|
||||
echo "remote repository URL is not configured" >&2
|
||||
|
|
@ -184,6 +208,7 @@ prepare_remote_web_repo() {
|
|||
repo_url_q="$(single_quote "$remote_web_repo_url")"
|
||||
repo_ref_q="$(single_quote "$remote_web_ref")"
|
||||
chrome_cmd="$(remote_chrome_export_command)"
|
||||
chrome_wrapper_cmd="$(remote_chrome_wrapper_setup_command)"
|
||||
|
||||
remote_exec "
|
||||
set -e
|
||||
|
|
@ -207,6 +232,7 @@ fi
|
|||
if [ -f python/pyproject.toml ]; then
|
||||
(cd python && python3 -m pip install -e '.[dev]')
|
||||
fi
|
||||
$chrome_wrapper_cmd
|
||||
$chrome_cmd
|
||||
printf 'remote Dart.web repo: %s\n' \"\$repo_dir\"
|
||||
printf 'remote Dart.web git ref: %s\n' \"\$(git rev-parse --short HEAD)\"
|
||||
|
|
|
|||
|
|
@ -53,5 +53,5 @@
|
|||
- 관련 경로: `agent-ops/skills/project/run-proto-socket-test-matrix/SKILL.md`, `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh`, `README.md`
|
||||
- 선행 작업: 사용 가능 언어 parity
|
||||
- 후속 작업: 안정화 기준선
|
||||
- 검증 기록: 2026-05-30에 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` 통과. Proto 동기화, Dart/Go/Kotlin/Python/TypeScript 동일 언어 테스트, 20개 일반 크로스 언어 방향, Dart.web WS 5개 방향이 모두 `PASS`.
|
||||
- 검증 기록: 2026-05-30에 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` 통과. Proto 동기화, Dart/Go/Kotlin/Python/TypeScript 동일 언어 테스트, 20개 일반 크로스 언어 방향, Dart.web WS/WSS 5개 방향이 모두 `PASS`.
|
||||
- 확인 필요: CI/CD 연결은 현재 완전성 기준이 아니다. 상위 `../oto` 프로젝트의 표준 실행 방식이 확정되고 사용자가 명시적으로 요청하면 이 저장소의 로컬 검증 진입점을 외부 실행기로 연결한다.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,6 @@
|
|||
- 관련 경로: `README.md`, `PROTOCOL.md`, `VERSIONING.md`, `PORTING_GUIDE.md`, `agent-roadmap/milestones/continuous-verification.md`, `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh`
|
||||
- 선행 작업: 지속 검증
|
||||
- 후속 작업: C# Unity/.NET 포트, Swift Apple 플랫폼 포트, 릴리즈 준비
|
||||
- 검증 근거: 2026-05-30에 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` 통과. Proto 동기화, Dart/Go/Kotlin/Python/TypeScript 동일 언어 테스트, 20개 일반 크로스 언어 방향, Dart.web WS 5개 방향이 모두 `PASS`.
|
||||
- 검증 근거: 2026-05-30에 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` 통과. Proto 동기화, Dart/Go/Kotlin/Python/TypeScript 동일 언어 테스트, 20개 일반 크로스 언어 방향, Dart.web WS/WSS 5개 방향이 모두 `PASS`.
|
||||
- 문서 정합성: 2026-05-22 기준 README, PROTOCOL, VERSIONING, PORTING_GUIDE의 protocol `0.1`, 지원 언어, 새 언어 보류, package registry 보류, validation gate 설명이 충돌하지 않는다.
|
||||
- 안정화 판단: 최신 전체 매트릭스 PASS와 문서 정합성 점검 기준으로 protocol `0.1` 및 현재 5개 언어 구현을 동결/완성형 후보로 둔다. 다만 coverage 완전성 평가는 라인 수치가 아니라 프로토콜 시나리오 coverage로 판단하며, Dart.web WSS self-signed certificate trust 자동화와 매트릭스 편입은 남은 coverage gap으로 기록한다. 새 변경 후보가 발견되기 전까지 protocol/API 변경은 compatibility work로만 다룬다.
|
||||
- 안정화 판단: 최신 전체 매트릭스 PASS와 문서 정합성 점검 기준으로 protocol `0.1` 및 현재 5개 언어 구현을 동결/완성형 후보로 둔다. Coverage 완전성 평가는 라인 수치가 아니라 프로토콜 시나리오 coverage로 판단하며, Dart.web WSS는 테스트용 self-signed certificate SPKI Chrome wrapper를 통해 매트릭스에 포함한다. 새 변경 후보가 발견되기 전까지 protocol/API 변경은 compatibility work로만 다룬다.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:proto_socket/proto_socket.dart';
|
|||
|
||||
const _host = '127.0.0.1';
|
||||
const _wsPort = 29098;
|
||||
const _wssPort = 29099;
|
||||
const _heartbeatIntervalSeconds = 30;
|
||||
const _heartbeatWaitSeconds = 10;
|
||||
const _serverObservationWindow = Duration(milliseconds: 500);
|
||||
|
|
@ -14,6 +15,8 @@ const _streamTimeout = Duration(seconds: 5);
|
|||
|
||||
final _repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
|
||||
final _dartDir = Directory('$_repoRoot/dart').path;
|
||||
final _certFile = '$_repoRoot/dart/test/certs/server.crt';
|
||||
final _keyFile = '$_repoRoot/dart/test/certs/server.key';
|
||||
|
||||
class _ServerWsClient extends WsProtobufClient {
|
||||
_ServerWsClient(WebSocket ws)
|
||||
|
|
@ -28,6 +31,10 @@ class _DartWebWsServer extends WsProtobufServer {
|
|||
_DartWebWsServer(int port, this.onConnected)
|
||||
: super(_host, port, (ws) => _ServerWsClient(ws));
|
||||
|
||||
_DartWebWsServer.secure(
|
||||
int port, SecurityContext securityContext, this.onConnected)
|
||||
: super.secure(_host, port, securityContext, (ws) => _ServerWsClient(ws));
|
||||
|
||||
@override
|
||||
void onClientConnected(WsProtobufClient client) => onConnected(client);
|
||||
}
|
||||
|
|
@ -36,9 +43,11 @@ Future<void> main() async {
|
|||
print(
|
||||
'INFO typeName dart=${TestData.getDefault().info_.qualifiedMessageName}');
|
||||
try {
|
||||
await _runWebWsSuite();
|
||||
print('PASS scenario=send-push');
|
||||
print('PASS scenario=request-response');
|
||||
await _runWebSuites();
|
||||
print('PASS scenario=ws-send-push');
|
||||
print('PASS scenario=ws-request-response');
|
||||
print('PASS scenario=wss-send-push');
|
||||
print('PASS scenario=wss-request-response');
|
||||
print('PASS all dart-server/dart-web-client crosstests passed');
|
||||
} catch (error) {
|
||||
stderr.writeln('FAIL crosstest error=$error');
|
||||
|
|
@ -46,35 +55,57 @@ Future<void> main() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _runWebWsSuite() async {
|
||||
final received = Completer<bool>();
|
||||
final server = _DartWebWsServer(_wsPort, (client) {
|
||||
client.addRequestListener<TestData, TestData>((req) async {
|
||||
print('SERVER_RECEIVED index=${req.index} message=${req.message}');
|
||||
if (req.index == 101 && req.message == 'fire from dart web client') {
|
||||
if (!received.isCompleted) {
|
||||
received.complete(true);
|
||||
}
|
||||
unawaited(client.send(TestData()
|
||||
..index = 200
|
||||
..message = 'push from dart server'));
|
||||
}
|
||||
return TestData()
|
||||
..index = req.index * 2
|
||||
..message = 'echo: ${req.message}';
|
||||
});
|
||||
Future<void> _runWebSuites() async {
|
||||
final wsReceived = Completer<bool>();
|
||||
final wssReceived = Completer<bool>();
|
||||
final securityContext = SecurityContext()
|
||||
..useCertificateChain(_certFile)
|
||||
..usePrivateKey(_keyFile);
|
||||
final wsServer = _DartWebWsServer(_wsPort, (client) {
|
||||
_configureWebClient(client, wsReceived);
|
||||
});
|
||||
final wssServer =
|
||||
_DartWebWsServer.secure(_wssPort, securityContext, (client) {
|
||||
_configureWebClient(client, wssReceived);
|
||||
});
|
||||
|
||||
await server.start();
|
||||
await wsServer.start();
|
||||
await wssServer.start();
|
||||
try {
|
||||
await _runDartBrowserTest('test/browser_ws_dart_io_test.dart');
|
||||
final ok = await received.future
|
||||
.timeout(_serverObservationWindow, onTimeout: () => false);
|
||||
if (!ok) {
|
||||
throw StateError('WS web send-push server did not receive expected data');
|
||||
}
|
||||
await _expectReceived('WS', wsReceived);
|
||||
await _expectReceived('WSS', wssReceived);
|
||||
} finally {
|
||||
await server.stop();
|
||||
await Future.wait([
|
||||
wssServer.stop(),
|
||||
wsServer.stop(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
void _configureWebClient(WsProtobufClient client, Completer<bool> received) {
|
||||
client.addRequestListener<TestData, TestData>((req) async {
|
||||
print('SERVER_RECEIVED index=${req.index} message=${req.message}');
|
||||
if (req.index == 101 && req.message == 'fire from dart web client') {
|
||||
if (!received.isCompleted) {
|
||||
received.complete(true);
|
||||
}
|
||||
unawaited(client.send(TestData()
|
||||
..index = 200
|
||||
..message = 'push from dart server'));
|
||||
}
|
||||
return TestData()
|
||||
..index = req.index * 2
|
||||
..message = 'echo: ${req.message}';
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _expectReceived(String transport, Completer<bool> received) async {
|
||||
final ok = await received.future
|
||||
.timeout(_serverObservationWindow, onTimeout: () => false);
|
||||
if (!ok) {
|
||||
throw StateError(
|
||||
'$transport web send-push server did not receive expected data');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,8 +132,8 @@ Future<void> _runDartBrowserTest(String testPath) async {
|
|||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
await Future.wait([stdoutDone, stderrDone]).timeout(_streamTimeout,
|
||||
onTimeout: () {
|
||||
throw StateError('dart browser test streams did not finish');
|
||||
});
|
||||
if (code != 0) {
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ void main() {
|
|||
port: 29098,
|
||||
serverPushMessage: 'push from dart server',
|
||||
);
|
||||
registerBrowserWsTests(
|
||||
serverLabel: 'dart.io',
|
||||
port: 29099,
|
||||
serverPushMessage: 'push from dart server',
|
||||
secure: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ void main() {
|
|||
port: 29498,
|
||||
serverPushMessage: 'push from kotlin server',
|
||||
);
|
||||
registerBrowserWsTests(
|
||||
serverLabel: 'kotlin',
|
||||
port: 29499,
|
||||
serverPushMessage: 'push from kotlin server',
|
||||
secure: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ void main() {
|
|||
port: 29698,
|
||||
serverPushMessage: 'push from python server',
|
||||
);
|
||||
registerBrowserWsTests(
|
||||
serverLabel: 'python',
|
||||
port: 29699,
|
||||
serverPushMessage: 'push from python server',
|
||||
secure: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,17 @@ void registerBrowserWsTests({
|
|||
required String serverLabel,
|
||||
required int port,
|
||||
required String serverPushMessage,
|
||||
bool secure = false,
|
||||
}) {
|
||||
group('Dart Web WsProtobufClient talks to $serverLabel WS server', () {
|
||||
final transportLabel = secure ? 'WSS' : 'WS';
|
||||
|
||||
group(
|
||||
'Dart Web WsProtobufClient talks to $serverLabel $transportLabel server',
|
||||
() {
|
||||
test('send-push: fire-and-forget and receive push', () async {
|
||||
final ws = await WsProtobufClient.connect(_host, port, path: _wsPath);
|
||||
final ws = secure
|
||||
? await WsProtobufClient.connectSecure(_host, port, path: _wsPath)
|
||||
: await WsProtobufClient.connect(_host, port, path: _wsPath);
|
||||
final client = _BrowserWsClient(ws);
|
||||
|
||||
final pushCompleter = Completer<TestData>();
|
||||
|
|
@ -46,7 +53,9 @@ void registerBrowserWsTests({
|
|||
});
|
||||
|
||||
test('request-response: single and concurrent requests', () async {
|
||||
final ws = await WsProtobufClient.connect(_host, port, path: _wsPath);
|
||||
final ws = secure
|
||||
? await WsProtobufClient.connectSecure(_host, port, path: _wsPath)
|
||||
: await WsProtobufClient.connect(_host, port, path: _wsPath);
|
||||
final client = _BrowserWsClient(ws);
|
||||
|
||||
final single = await client
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ void main() {
|
|||
port: 29198,
|
||||
serverPushMessage: 'push from go server',
|
||||
);
|
||||
registerBrowserWsTests(
|
||||
serverLabel: 'go',
|
||||
port: 29199,
|
||||
serverPushMessage: 'push from go server',
|
||||
secure: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ void main() {
|
|||
port: 29898,
|
||||
serverPushMessage: 'push from typescript server',
|
||||
);
|
||||
registerBrowserWsTests(
|
||||
serverLabel: 'typescript',
|
||||
port: 29899,
|
||||
serverPushMessage: 'push from typescript server',
|
||||
secure: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
|
@ -24,6 +25,7 @@ import (
|
|||
const (
|
||||
host = "127.0.0.1"
|
||||
goDartWebWSPort = 29198
|
||||
goDartWebWSSPort = 29199
|
||||
wsPath = "/"
|
||||
processTimeout = 60 * time.Second
|
||||
serverObservationWindow = 200 * time.Millisecond
|
||||
|
|
@ -50,25 +52,75 @@ func main() {
|
|||
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 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 {
|
||||
return runWebWSSuite()
|
||||
serverTLS, err := loadWebServerTLS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runWebSuites(serverTLS)
|
||||
}
|
||||
|
||||
func runWebWSSuite() error {
|
||||
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()
|
||||
|
||||
received := make(chan bool, 1)
|
||||
server := toki.NewWsServerWithOptions(host, goDartWebWSPort, wsPath, toki.WsServerOptions{
|
||||
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(),
|
||||
}, func(conn *websocket.Conn) *toki.WsClient {
|
||||
}
|
||||
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())
|
||||
|
|
@ -85,21 +137,17 @@ func runWebWSSuite() error {
|
|||
}, nil
|
||||
})
|
||||
}
|
||||
if err := server.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
defer server.Stop()
|
||||
return server
|
||||
}
|
||||
|
||||
if err := runDartBrowserTest(); err != nil {
|
||||
return err
|
||||
}
|
||||
func expectReceived(transport string, received <-chan bool) error {
|
||||
select {
|
||||
case ok := <-received:
|
||||
if !ok {
|
||||
return fmt.Errorf("WS web send-push server received unexpected data")
|
||||
return fmt.Errorf("%s web send-push server received unexpected data", transport)
|
||||
}
|
||||
case <-time.After(serverObservationWindow):
|
||||
return fmt.Errorf("WS web send-push server did not receive expected data")
|
||||
return fmt.Errorf("%s web send-push server did not receive expected data", transport)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@ func NewWsServerWithOptions(host string, port int, path string, options WsServer
|
|||
return s
|
||||
}
|
||||
|
||||
func NewWsServerTLSWithOptions(host string, port int, path string, tlsCfg *tls.Config, options WsServerOptions, newClient func(*websocket.Conn) *WsClient) *WsServer {
|
||||
s := NewWsServerTLS(host, port, path, tlsCfg, newClient)
|
||||
s.acceptOptions = options.AcceptOptions
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *WsServer) Started() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -12,16 +12,24 @@ import kotlinx.coroutines.CompletableDeferred
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.File
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private const val HOST = "127.0.0.1"
|
||||
private const val WS_PORT = 29498
|
||||
private const val WSS_PORT = 29499
|
||||
private const val WS_PATH = "/"
|
||||
private const val PROCESS_TIMEOUT_MS = 60_000L
|
||||
private const val SERVER_OBSERVATION_MS = 500L
|
||||
|
|
@ -30,9 +38,11 @@ private const val STREAM_JOIN_TIMEOUT_MS = 5_000L
|
|||
fun main() = runBlocking {
|
||||
println("INFO typeName kotlin=${typeNameOf<TestData>()}")
|
||||
try {
|
||||
runWebWs()
|
||||
println("PASS scenario=send-push")
|
||||
println("PASS scenario=request-response")
|
||||
runWebSuites()
|
||||
println("PASS scenario=ws-send-push")
|
||||
println("PASS scenario=ws-request-response")
|
||||
println("PASS scenario=wss-send-push")
|
||||
println("PASS scenario=wss-request-response")
|
||||
println("PASS all kotlin-server/dart-web-client crosstests passed")
|
||||
} catch (error: Throwable) {
|
||||
System.err.println("FAIL crosstest error=${error.message ?: error}")
|
||||
|
|
@ -40,17 +50,42 @@ fun main() = runBlocking {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun runWebWs() = coroutineScope {
|
||||
val received = CompletableDeferred<Boolean>()
|
||||
val server = WsServer(HOST, WS_PORT, WS_PATH) { conn ->
|
||||
private suspend fun runWebSuites() = coroutineScope {
|
||||
val wsReceived = CompletableDeferred<Boolean>()
|
||||
val wssReceived = CompletableDeferred<Boolean>()
|
||||
val certFile = kotlinResourceFile("server.crt")
|
||||
val keyFile = kotlinResourceFile("server.key")
|
||||
val serverCtx = buildServerSslContext(certFile.path, keyFile.path)
|
||||
val wsServer = webServer(WS_PORT, null, wsReceived)
|
||||
val wssServer = webServer(WSS_PORT, serverCtx, wssReceived)
|
||||
|
||||
wsServer.start()
|
||||
wssServer.start()
|
||||
delay(100)
|
||||
try {
|
||||
runDartBrowserTest("test/browser_ws_kotlin_test.dart")
|
||||
expectReceived("WS", wsReceived)
|
||||
expectReceived("WSS", wssReceived)
|
||||
} finally {
|
||||
wssServer.stop()
|
||||
wsServer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun webServer(
|
||||
port: Int,
|
||||
sslContext: SSLContext?,
|
||||
received: CompletableDeferred<Boolean>,
|
||||
): WsServer {
|
||||
val server = WsServer(HOST, port, WS_PATH, sslContext = sslContext) { conn ->
|
||||
WsClient.forServer(conn, 0, 0, parserMap())
|
||||
}
|
||||
server.onClientConnected = { client ->
|
||||
addRequestListenerTyped<TestData, TestData>(client.communicator) { req ->
|
||||
println("SERVER_RECEIVED index=${req.index} message=${req.message}")
|
||||
if (req.index == 101 && req.message == "fire from dart web client") {
|
||||
received.complete(true)
|
||||
launch {
|
||||
if (!received.isCompleted) received.complete(true)
|
||||
runBlocking {
|
||||
client.send(
|
||||
TestData.newBuilder()
|
||||
.setIndex(200)
|
||||
|
|
@ -65,15 +100,15 @@ private suspend fun runWebWs() = coroutineScope {
|
|||
.build()
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
delay(100)
|
||||
try {
|
||||
runDartBrowserTest("test/browser_ws_kotlin_test.dart")
|
||||
val ok = withTimeoutOrNull(SERVER_OBSERVATION_MS) { received.await() } ?: false
|
||||
check(ok) { "WS web send-push server did not receive expected data" }
|
||||
} finally {
|
||||
server.stop()
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
private suspend fun expectReceived(
|
||||
transport: String,
|
||||
received: CompletableDeferred<Boolean>,
|
||||
) {
|
||||
val ok = withTimeoutOrNull(SERVER_OBSERVATION_MS) { received.await() } ?: false
|
||||
check(ok) { "$transport web send-push server did not receive expected data" }
|
||||
}
|
||||
|
||||
private fun destroyProcessTree(process: Process) {
|
||||
|
|
@ -112,6 +147,39 @@ private suspend fun runDartBrowserTest(testPath: String) = withContext(Dispatche
|
|||
private fun parserMap(): ParserMap =
|
||||
mapOf(typeNameOf<TestData>() to { TestData.parseFrom(it) })
|
||||
|
||||
private fun buildServerSslContext(certPath: String, keyPath: String): SSLContext {
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
val cert = File(certPath).inputStream().use {
|
||||
certFactory.generateCertificate(it) as X509Certificate
|
||||
}
|
||||
val keyPem = File(keyPath).readText()
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.replace("\\s+".toRegex(), "")
|
||||
val privateKey = KeyFactory.getInstance("RSA")
|
||||
.generatePrivate(PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPem)))
|
||||
val keyStore = KeyStore.getInstance("PKCS12")
|
||||
keyStore.load(null, null)
|
||||
keyStore.setKeyEntry("toki", privateKey, CharArray(0), arrayOf(cert))
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
keyManagerFactory.init(keyStore, CharArray(0))
|
||||
val ctx = SSLContext.getInstance("TLS")
|
||||
ctx.init(keyManagerFactory.keyManagers, null, null)
|
||||
return ctx
|
||||
}
|
||||
|
||||
private fun kotlinResourceFile(name: String): File {
|
||||
var dir = File(System.getProperty("user.dir")).absoluteFile
|
||||
while (dir.parentFile != null) {
|
||||
val direct = File(dir, "src/test/resources/$name").canonicalFile
|
||||
if (direct.isFile) return direct
|
||||
val nested = File(dir, "kotlin/src/test/resources/$name").canonicalFile
|
||||
if (nested.isFile) return nested
|
||||
dir = dir.parentFile
|
||||
}
|
||||
error("cannot resolve kotlin test resource $name")
|
||||
}
|
||||
|
||||
private fun dartDir(): File {
|
||||
var dir = File(System.getProperty("user.dir")).absoluteFile
|
||||
while (dir.parentFile != null) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -12,9 +13,12 @@ from proto_socket.ws_server import WsServer
|
|||
|
||||
HOST = "127.0.0.1"
|
||||
WS_PORT = 29698
|
||||
WSS_PORT = 29699
|
||||
WS_PATH = "/"
|
||||
PROCESS_TIMEOUT = 60.0
|
||||
SERVER_OBSERVATION_WINDOW = 0.5
|
||||
CERT_PATH = Path(__file__).resolve().parents[1] / "test" / "certs" / "server.crt"
|
||||
KEY_PATH = Path(__file__).resolve().parents[1] / "test" / "certs" / "server.key"
|
||||
|
||||
|
||||
def parser_map():
|
||||
|
|
@ -24,18 +28,49 @@ def parser_map():
|
|||
async def main() -> None:
|
||||
print(f"INFO typeName python={type_name_of(TestData)}")
|
||||
try:
|
||||
await run_web_ws()
|
||||
await run_web_suites()
|
||||
except Exception as exc:
|
||||
print(f"FAIL crosstest error={exc}", file=sys.stderr)
|
||||
raise SystemExit(1) from exc
|
||||
print("PASS scenario=send-push")
|
||||
print("PASS scenario=request-response")
|
||||
print("PASS scenario=ws-send-push")
|
||||
print("PASS scenario=ws-request-response")
|
||||
print("PASS scenario=wss-send-push")
|
||||
print("PASS scenario=wss-request-response")
|
||||
print("PASS all python-server/dart-web-client crosstests passed")
|
||||
|
||||
|
||||
async def run_web_ws() -> None:
|
||||
received = asyncio.get_running_loop().create_future()
|
||||
server = WsServer(HOST, WS_PORT, WS_PATH, interval_sec=0, wait_sec=0, parser_map=parser_map())
|
||||
async def run_web_suites() -> None:
|
||||
loop = asyncio.get_running_loop()
|
||||
ws_received = loop.create_future()
|
||||
wss_received = loop.create_future()
|
||||
ws_server = make_web_server(WS_PORT, None, ws_received)
|
||||
wss_server = make_web_server(WSS_PORT, make_server_ssl_context(), wss_received)
|
||||
|
||||
await ws_server.start()
|
||||
await wss_server.start()
|
||||
try:
|
||||
await run_dart_browser_test("test/browser_ws_python_test.dart")
|
||||
await expect_received("WS", ws_received)
|
||||
await expect_received("WSS", wss_received)
|
||||
finally:
|
||||
await wss_server.stop()
|
||||
await ws_server.stop()
|
||||
|
||||
|
||||
def make_web_server(
|
||||
port: int,
|
||||
ssl_context: ssl.SSLContext | None,
|
||||
received: asyncio.Future[bool],
|
||||
) -> WsServer:
|
||||
server = WsServer(
|
||||
HOST,
|
||||
port,
|
||||
WS_PATH,
|
||||
interval_sec=0,
|
||||
wait_sec=0,
|
||||
parser_map=parser_map(),
|
||||
ssl_context=ssl_context,
|
||||
)
|
||||
|
||||
def on_connected(client):
|
||||
def on_request(req):
|
||||
|
|
@ -53,14 +88,24 @@ async def run_web_ws() -> None:
|
|||
client.communicator.add_request_listener(type_name_of(TestData), on_request)
|
||||
|
||||
server.on_client_connected = on_connected
|
||||
await server.start()
|
||||
return server
|
||||
|
||||
|
||||
def make_server_ssl_context() -> ssl.SSLContext:
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ctx.load_cert_chain(CERT_PATH, KEY_PATH)
|
||||
return ctx
|
||||
|
||||
|
||||
async def expect_received(transport: str, received: asyncio.Future[bool]) -> None:
|
||||
try:
|
||||
await run_dart_browser_test("test/browser_ws_python_test.dart")
|
||||
ok = await asyncio.wait_for(received, SERVER_OBSERVATION_WINDOW)
|
||||
if not ok:
|
||||
raise RuntimeError("WS web send-push server received unexpected data")
|
||||
finally:
|
||||
await server.stop()
|
||||
except TimeoutError as exc:
|
||||
raise RuntimeError(
|
||||
f"{transport} web send-push server did not receive expected data"
|
||||
) from exc
|
||||
if not ok:
|
||||
raise RuntimeError(f"{transport} web send-push server received unexpected data")
|
||||
|
||||
|
||||
async def run_dart_browser_test(test_path: str) -> None:
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@
|
|||
|
||||
- [ ] Record exact commands used to run TypeScript checks and Go cross tests.
|
||||
- [ ] Record fixed ports `29490` and `29492`.
|
||||
- [ ] Record TLS/WSS coverage status. Non-browser TLS/WSS is required; Dart.web WSS remains a required browser trust automation gap until self-signed test CA/certificate handling is added.
|
||||
- [ ] Record TLS/WSS coverage status. Non-browser TLS/WSS is required; Dart.web WSS is covered through the browser matrix with the test certificate SPKI Chrome wrapper.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as childProcess from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as readline from "node:readline";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
|
@ -14,9 +15,12 @@ import { NodeWsServer } from "../src/node_ws_server.js";
|
|||
const __filename = fileURLToPath(import.meta.url);
|
||||
const repoRoot = path.resolve(path.dirname(__filename), "../..");
|
||||
const dartDir = path.join(repoRoot, "dart");
|
||||
const CERT_PATH = path.join(repoRoot, "typescript/test/certs/server.crt");
|
||||
const KEY_PATH = path.join(repoRoot, "typescript/test/certs/server.key");
|
||||
|
||||
const HOST = "127.0.0.1";
|
||||
const WS_PORT = 29898;
|
||||
const WSS_PORT = 29899;
|
||||
const WS_PATH = "/";
|
||||
const PROCESS_TIMEOUT_MS = 60_000;
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
|
|
@ -25,9 +29,11 @@ const SERVER_OBSERVATION_MS = 500;
|
|||
async function main(): Promise<void> {
|
||||
console.log(`INFO typeName ts=${TestDataSchema.typeName}`);
|
||||
try {
|
||||
await runWebWs();
|
||||
console.log("PASS scenario=send-push");
|
||||
console.log("PASS scenario=request-response");
|
||||
await runWebSuites();
|
||||
console.log("PASS scenario=ws-send-push");
|
||||
console.log("PASS scenario=ws-request-response");
|
||||
console.log("PASS scenario=wss-send-push");
|
||||
console.log("PASS scenario=wss-request-response");
|
||||
console.log("PASS all typescript-server/dart-web-client crosstests passed");
|
||||
} catch (err) {
|
||||
console.error(`FAIL crosstest error=${errorMessage(err)}`);
|
||||
|
|
@ -35,20 +41,40 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function runWebWs(): Promise<void> {
|
||||
let resolveReceived: ((value: boolean) => void) | null = null;
|
||||
const received = new Promise<boolean>((resolve) => {
|
||||
resolveReceived = resolve;
|
||||
});
|
||||
const server = new NodeWsServer(HOST, WS_PORT, WS_PATH, (ws) => new NodeWsClient(ws, 0, 0, parserMap()));
|
||||
async function runWebSuites(): Promise<void> {
|
||||
const wsReceived = createReceivedSignal();
|
||||
const wssReceived = createReceivedSignal();
|
||||
const wsServer = createWebServer(WS_PORT, wsReceived.resolve);
|
||||
const wssServer = createWebServer(WSS_PORT, wssReceived.resolve, serverTlsOptions());
|
||||
|
||||
await wsServer.start();
|
||||
await wssServer.start();
|
||||
try {
|
||||
await runDartBrowserTest("test/browser_ws_typescript_test.dart");
|
||||
const wsOk = await withTimeout(wsReceived.promise, SERVER_OBSERVATION_MS, "WS web send-push server did not receive expected data");
|
||||
if (!wsOk) {
|
||||
throw new Error("WS web send-push server received unexpected data");
|
||||
}
|
||||
const wssOk = await withTimeout(wssReceived.promise, SERVER_OBSERVATION_MS, "WSS web send-push server did not receive expected data");
|
||||
if (!wssOk) {
|
||||
throw new Error("WSS web send-push server received unexpected data");
|
||||
}
|
||||
} finally {
|
||||
await Promise.allSettled([wssServer.stop(), wsServer.stop()]);
|
||||
}
|
||||
}
|
||||
|
||||
function createWebServer(
|
||||
port: number,
|
||||
resolveReceived: (value: boolean) => void,
|
||||
tlsOptions?: { cert: Buffer; key: Buffer },
|
||||
): NodeWsServer {
|
||||
const server = new NodeWsServer(HOST, port, WS_PATH, (ws) => new NodeWsClient(ws, 0, 0, parserMap()), tlsOptions);
|
||||
server.onClientConnected = (client) => {
|
||||
addRequestListenerTyped(client.communicator, TestDataSchema, (req) => {
|
||||
console.log(`SERVER_RECEIVED index=${req.index} message=${req.message}`);
|
||||
if (req.index === 101 && req.message === "fire from dart web client") {
|
||||
if (resolveReceived !== null) {
|
||||
resolveReceived(true);
|
||||
resolveReceived = null;
|
||||
}
|
||||
resolveReceived(true);
|
||||
void client.communicator.send(
|
||||
create(TestDataSchema, {
|
||||
index: 200,
|
||||
|
|
@ -62,16 +88,7 @@ async function runWebWs(): Promise<void> {
|
|||
});
|
||||
});
|
||||
};
|
||||
await server.start();
|
||||
try {
|
||||
await runDartBrowserTest("test/browser_ws_typescript_test.dart");
|
||||
const ok = await withTimeout(received, SERVER_OBSERVATION_MS, "WS web send-push server did not receive expected data");
|
||||
if (!ok) {
|
||||
throw new Error("WS web send-push server received unexpected data");
|
||||
}
|
||||
} finally {
|
||||
await server.stop();
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
async function runDartBrowserTest(testPath: string): Promise<void> {
|
||||
|
|
@ -130,6 +147,33 @@ function parserMap() {
|
|||
return new Map([[TestDataSchema.typeName, parserFromSchema(TestDataSchema)]]);
|
||||
}
|
||||
|
||||
function createReceivedSignal(): {
|
||||
promise: Promise<boolean>;
|
||||
resolve: (value: boolean) => void;
|
||||
} {
|
||||
let done = false;
|
||||
let resolvePromise: (value: boolean) => void;
|
||||
const promise = new Promise<boolean>((resolve) => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
resolve: (value: boolean) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
resolvePromise(value);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function serverTlsOptions(): { cert: Buffer; key: Buffer } {
|
||||
return {
|
||||
cert: fs.readFileSync(CERT_PATH),
|
||||
key: fs.readFileSync(KEY_PATH),
|
||||
};
|
||||
}
|
||||
|
||||
function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
if (process.platform !== "win32" && child.pid !== undefined) {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue