docs: update cross-language crosstest commands in README and add dart_typescript/go_typescript/kotlin_* python_typescript crosstest files

This commit is contained in:
toki 2026-05-21 14:54:01 +09:00
parent f0da42c63a
commit a3f3ccf4b5
7 changed files with 92 additions and 20 deletions

View file

@ -196,16 +196,40 @@ Cross-language checks:
cd go
go run ./crosstest/go_dart.go
go run ./crosstest/go_kotlin.go
go run ./crosstest/go_python.go
go run ./crosstest/go_typescript.go
```
```bash
cd dart
dart run crosstest/dart_go.dart
dart run crosstest/dart_kotlin.dart
dart run crosstest/dart_python.dart
dart run crosstest/dart_typescript.dart
```
```bash
cd kotlin
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
```
```bash
cd python
python3 crosstest/python_dart.py
python3 crosstest/python_go.py
python3 crosstest/python_kotlin.py
python3 crosstest/python_typescript.py
```
```bash
cd typescript
./node_modules/.bin/tsx crosstest/typescript_dart.ts
./node_modules/.bin/tsx crosstest/typescript_go.ts
./node_modules/.bin/tsx crosstest/typescript_kotlin.ts
./node_modules/.bin/tsx crosstest/typescript_python.ts
```
When proto files change, also run:

View file

@ -13,6 +13,7 @@ const _heartbeatIntervalSeconds = 30;
const _heartbeatWaitSeconds = 10;
const _serverObservationWindow = Duration(milliseconds: 200);
const _processTimeout = Duration(seconds: 20);
const _streamTimeout = Duration(seconds: 5);
final _repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
final _typescriptDir = Directory('$_repoRoot/typescript').path;
final _certFile = '$_repoRoot/dart/test/certs/server.crt';
@ -295,10 +296,10 @@ Future<void> _runWssRequests(SecurityContext ctx) async {
Future<void> _runTypescriptClient(
String mode, int port, String phase, Set<String> expectedScenarios,
{String? certFile}) async {
final tsxBin = '$_typescriptDir/node_modules/.bin/tsx';
final process = await Process.start(
'npx',
tsxBin,
[
'tsx',
'crosstest/dart_typescript_client.ts',
'--mode=$mode',
'--port=$port',
@ -329,7 +330,10 @@ Future<void> _runTypescriptClient(
process.kill(ProcessSignal.sigkill);
return -1;
});
await Future.wait([stdoutDone, stderrDone]);
await Future.wait([stdoutDone, stderrDone])
.timeout(_streamTimeout, onTimeout: () {
throw StateError('typescript-client $mode/$phase streams did not finish');
});
_validateResultLines(
'typescript-client $mode/$phase',

View file

@ -367,8 +367,8 @@ func runTypescriptClient(mode string, port int, phase string, expected map[strin
return err
}
tsxBin := filepath.Join(typescriptDir, "node_modules", ".bin", "tsx")
args := []string{
"tsx",
"crosstest/go_typescript_client.ts",
"--mode=" + mode,
fmt.Sprintf("--port=%d", port),
@ -377,8 +377,9 @@ func runTypescriptClient(mode string, port int, phase string, expected map[strin
if len(certFile) > 0 && certFile[0] != "" {
args = append(args, "--cert="+certFile[0])
}
cmd := exec.CommandContext(ctx, "npx", args...)
cmd := exec.CommandContext(ctx, tsxBin, args...)
cmd.Dir = typescriptDir
cmd.WaitDelay = 5 * time.Second
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
@ -400,7 +401,13 @@ func runTypescriptClient(mode string, port int, phase string, expected map[strin
go scanLines(&wg, stderrPipe, os.Stderr, nil, nil)
waitErr := cmd.Wait()
wg.Wait()
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
select {
case <-done:
case <-time.After(5 * time.Second):
return fmt.Errorf("typescript client %s/%s pipe scan timed out", mode, phase)
}
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("typescript client %s/%s timed out", mode, phase)
}

View file

@ -39,6 +39,7 @@ private const val WSS_PORT = 29496
private const val WS_PATH = "/"
private const val PROCESS_TIMEOUT_MS = 20_000L
private const val SERVER_OBSERVATION_MS = 200L
private const val STREAM_JOIN_TIMEOUT_MS = 5_000L
fun main() = runBlocking {
println("INFO typeName kotlin=${typeNameOf<TestData>()}")
@ -279,6 +280,11 @@ private suspend fun withServer(
}
}
private fun destroyProcessTree(process: Process) {
process.toHandle().descendants().forEach { it.destroyForcibly() }
process.destroyForcibly()
}
private suspend fun runDartClient(
mode: String,
port: Int,
@ -321,13 +327,17 @@ private suspend fun runDartClient(
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
process.destroyForcibly()
destroyProcessTree(process)
}
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
val label = "dart-client $mode/$phase"
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
"$label stream readers did not finish"
}
stdoutThread.join()
stderrThread.join()
validateResultLines(
"dart-client $mode/$phase",
label,
if (finished) process.exitValue() else -1,
stdoutLines,
expectedScenarios,

View file

@ -39,6 +39,7 @@ private const val WSS_PORT = 29396
private const val WS_PATH = "/"
private const val PROCESS_TIMEOUT_MS = 20_000L
private const val SERVER_OBSERVATION_MS = 200L
private const val STREAM_JOIN_TIMEOUT_MS = 5_000L
fun main() = runBlocking {
println("INFO typeName kotlin=${typeNameOf<TestData>()}")
@ -279,6 +280,11 @@ private suspend fun withServer(
}
}
private fun destroyProcessTree(process: Process) {
process.toHandle().descendants().forEach { it.destroyForcibly() }
process.destroyForcibly()
}
private suspend fun runGoClient(
mode: String,
port: Int,
@ -321,13 +327,17 @@ private suspend fun runGoClient(
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
process.destroyForcibly()
destroyProcessTree(process)
}
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
val label = "go-client $mode/$phase"
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
"$label stream readers did not finish"
}
stdoutThread.join()
stderrThread.join()
validateResultLines(
"go-client $mode/$phase",
label,
if (finished) process.exitValue() else -1,
stdoutLines,
expectedScenarios,

View file

@ -39,6 +39,7 @@ private const val WSS_PORT = 29400
private const val WS_PATH = "/"
private const val PROCESS_TIMEOUT_MS = 20_000L
private const val SERVER_OBSERVATION_MS = 200L
private const val STREAM_JOIN_TIMEOUT_MS = 5_000L
fun main() = runBlocking {
println("INFO typeName kotlin=${typeNameOf<TestData>()}")
@ -279,6 +280,11 @@ private suspend fun withServer(
}
}
private fun destroyProcessTree(process: Process) {
process.toHandle().descendants().forEach { it.destroyForcibly() }
process.destroyForcibly()
}
private suspend fun runPythonClient(
mode: String,
port: Int,
@ -320,13 +326,17 @@ private suspend fun runPythonClient(
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
process.destroyForcibly()
destroyProcessTree(process)
}
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
val label = "python-client $mode/$phase"
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
"$label stream readers did not finish"
}
stdoutThread.join()
stderrThread.join()
validateResultLines(
"python-client $mode/$phase",
label,
if (finished) process.exitValue() else -1,
stdoutLines,
expectedScenarios,

View file

@ -1,7 +1,9 @@
from __future__ import annotations
import asyncio
import os
import re
import signal
import ssl
import sys
from pathlib import Path
@ -266,8 +268,9 @@ async def run_typescript_client(
cert: Path | None = None,
) -> None:
ts_dir = typescript_package_dir()
tsx_bin = ts_dir / "node_modules" / ".bin" / "tsx"
args = [
"tsx",
str(tsx_bin),
"crosstest/python_typescript_client.ts",
f"--mode={mode}",
f"--port={port}",
@ -276,15 +279,19 @@ async def run_typescript_client(
if cert is not None:
args.append(f"--cert={cert}")
process = await asyncio.create_subprocess_exec(
"npx",
*args,
cwd=ts_dir,
start_new_session=True,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout, stderr = await asyncio.wait_for(process.communicate(), PROCESS_TIMEOUT)
except TimeoutError as exc:
try:
os.killpg(process.pid, signal.SIGKILL)
except ProcessLookupError:
pass
process.kill()
await process.wait()
raise TimeoutError(f"typescript client {mode}/{phase} timed out") from exc