교차 테스트 프로세스 정리 및 문서 업데이트 (Go, Dart, TypeScript, Python)
This commit is contained in:
parent
efb8917178
commit
cffebc0fc7
42 changed files with 3746 additions and 25 deletions
|
|
@ -113,10 +113,10 @@ On the receive side, use the same value as the registration key.
|
|||
| C# | `typeof(T).Name` — verify matches proto qualified name |
|
||||
| Kotlin | `descriptorForType.fullName` via `typeNameOf(m)` |
|
||||
| Swift | `String(describing: T.self)` — verify matches |
|
||||
| TypeScript | `MessageType.typeName` (protobuf-es) — verify matches |
|
||||
| TypeScript | local `MessageType.typeName` value — verify matches |
|
||||
| Python | `descriptor.name` from `MessageClass.DESCRIPTOR` — verify matches |
|
||||
|
||||
The current packet proto has no `package` declaration, so Dart, Go, and Kotlin all use simple names such as `TestData` and `HeartBeat`. If a future proto adds a `package`, `proto.MessageName` may become fully qualified, and all implementations must use the same value.
|
||||
The current packet proto has no `package` declaration, so available implementations use simple names such as `TestData` and `HeartBeat`. If a future proto adds a `package`, language runtimes may derive fully qualified names, and all implementations must use the same value.
|
||||
|
||||
**Important**: Verify typeName consistency across languages before connecting heterogeneous clients.
|
||||
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -172,7 +172,7 @@ If you want a copyable scaffold for using this module from another repository, s
|
|||
|
||||
## Running Tests
|
||||
|
||||
Local commands are documented here for development and troubleshooting. Continuous verification is expected to run in an external tool, with Jenkins planned to execute the full Dart, Go, and cross-language test suite.
|
||||
Local commands are documented here for development and troubleshooting. Continuous verification is expected to run in an external tool, with Jenkins planned to execute the full supported-language and cross-language test suite.
|
||||
|
||||
```bash
|
||||
cd dart
|
||||
|
|
@ -190,6 +190,17 @@ cd kotlin
|
|||
./gradlew test
|
||||
```
|
||||
|
||||
```bash
|
||||
cd python
|
||||
python3 -m pytest -q
|
||||
```
|
||||
|
||||
```bash
|
||||
cd typescript
|
||||
npm run check
|
||||
npm test
|
||||
```
|
||||
|
||||
Cross-language checks:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Proto Socket tracks protocol compatibility separately from language package vers
|
|||
- Dart package version: see `dart/pubspec.yaml`
|
||||
- Go module version: repository tag based
|
||||
|
||||
`0.1` is the current compatibility contract for the checked-in Dart and Go implementations. The wire format does not carry a protocol version field yet; compatibility is verified by shared proto definitions, unit tests, and cross-language tests.
|
||||
`0.1` is the current compatibility contract for the checked-in available implementations. The wire format does not carry a protocol version field yet; compatibility is verified by shared proto definitions, unit tests, and cross-language tests.
|
||||
|
||||
## Protocol Version
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ Changing this wrap behavior or the reserved meaning of `responseNonce == 0` chan
|
|||
A new language implementation must target the current protocol version unless its README explicitly says otherwise. Before it is listed as available, it must:
|
||||
|
||||
- Generate protobuf bindings from the canonical schema.
|
||||
- Use the same `typeName` values as Dart and Go.
|
||||
- Use the same `typeName` values as the available implementations.
|
||||
- Implement TCP, TLS+TCP, WS, and WSS framing consistently where the language runtime supports them.
|
||||
- Implement request-response correlation with `nonce` and `responseNonce`.
|
||||
- Handle heartbeat automatically inside the client/server core.
|
||||
|
|
|
|||
|
|
@ -70,3 +70,4 @@ VERSIONING.md — 프로토콜/패키지 버전 정책
|
|||
| 요청 키워드 | SKILL.md |
|
||||
|------------|----------|
|
||||
| 새 언어 구현, 언어 포팅, language scaffold, 템플릿, 크로스 언어 테스트 추가, 새 언어 crosstest | `agent-ops/skills/project/add-proto-socket-crosstest-language/SKILL.md` |
|
||||
| 전체 테스트해, 전체테스트해, 전체 검증, 테스트 매트릭스, 언어간 크로스 테스트해, 언어별 크로스 테스트, 크로스 테스트해, 통신 테스트 | `agent-ops/skills/project/run-proto-socket-test-matrix/SKILL.md` |
|
||||
|
|
|
|||
120
agent-ops/skills/project/run-proto-socket-test-matrix/SKILL.md
Normal file
120
agent-ops/skills/project/run-proto-socket-test-matrix/SKILL.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
name: run-proto-socket-test-matrix
|
||||
description: Run and summarize the Proto Socket validation matrix across Dart, Go, Kotlin, Python, and TypeScript. Use when the user asks for "전체 테스트", "전체테스트해", "전체 검증", "테스트 매트릭스", "언어별 크로스 테스트", "언어간 크로스 테스트", "크로스 테스트해", "통신 테스트", or similar requests that require same-language unit/transport tests plus cross-language server/client communication checks with PASS/FAIL tables.
|
||||
---
|
||||
|
||||
# Run Proto Socket Test Matrix
|
||||
|
||||
## 목적
|
||||
|
||||
Proto Socket의 지원 언어 전체 테스트 상태를 재현 가능한 명령으로 확인하고, 사용자에게 `PASS`/`FAIL` 표로 보고한다. 기본 범위는 동일 언어 테스트와 언어간 크로스 통신 테스트 전체이며, 요청이 크로스 테스트만이면 크로스 범위만 실행한다.
|
||||
|
||||
## 언제 호출할지
|
||||
|
||||
- 사용자가 “전체 테스트해”, “전체테스트”, “전체 검증”, “테스트 매트릭스”처럼 전체 검증을 요청할 때
|
||||
- 사용자가 “언어간 크로스 테스트해”, “언어별 크로스 테스트”, “통신 테스트”처럼 언어 간 통신 검증을 요청할 때
|
||||
- 새 언어 추가, 통신 구현 변경, 테스트 정리 후 지원 언어 매트릭스의 현재 통과 여부를 표로 정리해야 할 때
|
||||
|
||||
## 입력
|
||||
|
||||
- `scope`: `all`, `unit`, `cross` 중 하나. 사용자가 범위를 지정하지 않으면 `all`.
|
||||
- `report-format`: 기본은 Markdown 표. 실패 시 실패 로그 tail과 재현 명령을 함께 적는다.
|
||||
|
||||
## 먼저 확인할 것
|
||||
|
||||
- [ ] 세션에서 아직 읽지 않았다면 `agent-ops/rules/project/rules.md`와, 존재할 경우 `agent-ops/rules/private/rules.md`를 읽는다.
|
||||
- [ ] 테스트만 실행할 때는 소스 수정 계획을 세우지 않는다. 실패 원인 수정까지 요청받은 경우에만 관련 domain rule을 읽고 코드 변경한다.
|
||||
- [ ] `agent-task/archive/**`는 사용자가 명시적으로 요청한 경우에만 읽는다.
|
||||
- [ ] 고정 포트를 쓰는 크로스테스트가 있으므로 크로스테스트는 순차 실행한다.
|
||||
|
||||
## 실행 절차
|
||||
|
||||
1. **범위 결정**
|
||||
- “전체 테스트”, “전체 검증”, “테스트 매트릭스” 요청은 `all`로 실행한다.
|
||||
- “언어간 크로스 테스트”, “크로스 테스트”, “통신 테스트” 요청은 사용자가 유닛 테스트도 요구하지 않는 한 `cross`로 실행한다.
|
||||
- “유닛 테스트”, “동일 언어 테스트” 요청은 `unit`으로 실행한다.
|
||||
|
||||
2. **스크립트 실행**
|
||||
- 저장소 루트에서 다음 중 하나를 실행한다.
|
||||
|
||||
```bash
|
||||
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
|
||||
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --unit
|
||||
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --cross
|
||||
```
|
||||
|
||||
3. **수동 fallback**
|
||||
- 스크립트를 쓸 수 없으면 README의 package-local 명령을 사용한다.
|
||||
- 동일 언어 테스트:
|
||||
|
||||
```bash
|
||||
(cd dart && dart test)
|
||||
(cd go && go test ./...)
|
||||
(cd kotlin && ./gradlew test)
|
||||
(cd python && python3 -m pytest -q)
|
||||
(cd typescript && npm run check && npm test)
|
||||
```
|
||||
|
||||
- 크로스테스트는 서버 언어 행 기준으로 순차 실행한다.
|
||||
|
||||
```bash
|
||||
(cd go && go run ./crosstest/go_dart.go)
|
||||
(cd go && go run ./crosstest/go_kotlin.go)
|
||||
(cd go && go run ./crosstest/go_python.go)
|
||||
(cd go && go run ./crosstest/go_typescript.go)
|
||||
(cd dart && dart run crosstest/dart_go.dart)
|
||||
(cd dart && dart run crosstest/dart_kotlin.dart)
|
||||
(cd dart && dart run crosstest/dart_python.dart)
|
||||
(cd dart && dart run crosstest/dart_typescript.dart)
|
||||
(cd kotlin && ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt)
|
||||
(cd kotlin && ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt)
|
||||
(cd kotlin && ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt)
|
||||
(cd kotlin && ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt)
|
||||
(cd python && python3 crosstest/python_dart.py)
|
||||
(cd python && python3 crosstest/python_go.py)
|
||||
(cd python && python3 crosstest/python_kotlin.py)
|
||||
(cd python && python3 crosstest/python_typescript.py)
|
||||
(cd typescript && ./node_modules/.bin/tsx crosstest/typescript_dart.ts)
|
||||
(cd typescript && ./node_modules/.bin/tsx crosstest/typescript_go.ts)
|
||||
(cd typescript && ./node_modules/.bin/tsx crosstest/typescript_kotlin.ts)
|
||||
(cd typescript && ./node_modules/.bin/tsx crosstest/typescript_python.ts)
|
||||
```
|
||||
|
||||
4. **결과 판정**
|
||||
- 명령 종료 코드가 0이고 `FAIL` 라인이 없으면 `PASS`.
|
||||
- 크로스테스트는 각 방향의 `PASS scenario=...` 라인 수가 기대 개수와 일치하는지도 검증한다.
|
||||
- 동일 언어 대각선 셀은 `unit` 또는 `all` 범위에서 해당 언어 동일 언어 테스트가 통과했을 때 `PASS`로 표시한다. `cross`만 실행했다면 대각선은 `-`로 둔다.
|
||||
|
||||
5. **결과 보고**
|
||||
- 동일 언어 테스트 표와 서버/클라이언트 크로스 통신 표를 한국어로 보고한다.
|
||||
- 실패가 있으면 실패 행을 먼저 보여주고, 실패 로그 tail과 재현 명령을 적는다.
|
||||
- 코드 변경은 하지 않았는지, 테스트 실행으로 생성된 캐시/빌드 산출물 외 작업트리 변경이 있는지 확인해 함께 알린다.
|
||||
|
||||
## 실행 결과 검증
|
||||
|
||||
- [ ] 요청 범위의 모든 명령이 실행되었는가
|
||||
- [ ] 실패 명령의 재현 명령과 로그 요약을 남겼는가
|
||||
- [ ] 최종 답변에 `PASS`/`FAIL` 표가 포함되었는가
|
||||
- [ ] `git status --short`로 의도치 않은 소스 변경이 없는지 확인했는가
|
||||
- 검증 실패 시: 누락된 명령을 실행하거나, 실행 불가 사유와 미검증 범위를 명확히 보고한다.
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```markdown
|
||||
**동일언어**
|
||||
| 언어 | 명령 | 결과 |
|
||||
|---|---|---|
|
||||
| Dart | `dart test` | PASS |
|
||||
|
||||
**언어간 통신**
|
||||
| 서버 \ 클라이언트 | Dart | Go | Kotlin | Python | TypeScript |
|
||||
|---|---|---|---|---|---|
|
||||
| Dart | PASS | PASS | PASS | PASS | PASS |
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- 크로스테스트를 병렬 실행하지 않는다.
|
||||
- 테스트 요청만 받았는데 소스 코드를 수정하지 않는다.
|
||||
- 실패를 통과로 단정하지 않는다. 불확실하면 미검증 또는 실패 후보로 표시한다.
|
||||
- `agent-task/archive/**`를 자동으로 읽지 않는다.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
interface:
|
||||
display_name: "Proto Socket Test Matrix"
|
||||
short_description: "Run full Proto Socket test matrix"
|
||||
default_prompt: "Use $run-proto-socket-test-matrix to run the full Proto Socket unit and cross-language test matrix."
|
||||
254
agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh
Executable file
254
agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh
Executable file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: run_matrix.sh [--all|--unit|--cross]
|
||||
|
||||
Runs Proto Socket unit/same-language tests and/or cross-language communication
|
||||
tests, then prints Markdown PASS/FAIL tables. Defaults to --all.
|
||||
USAGE
|
||||
}
|
||||
|
||||
mode="all"
|
||||
case "${1:-}" in
|
||||
""|--all) mode="all" ;;
|
||||
--unit) mode="unit" ;;
|
||||
--cross) mode="cross" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) usage >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "$script_dir/../../../../.." && pwd)"
|
||||
log_dir="$(mktemp -d "${TMPDIR:-/tmp}/proto-socket-matrix.XXXXXX")"
|
||||
|
||||
languages=("Dart" "Go" "Kotlin" "Python" "TypeScript")
|
||||
|
||||
declare -A unit_cmds=(
|
||||
["Dart"]="dart test"
|
||||
["Go"]="go test ./..."
|
||||
["Kotlin"]="./gradlew test"
|
||||
["Python"]="python3 -m pytest -q"
|
||||
["TypeScript"]="npm run check && npm test"
|
||||
)
|
||||
declare -A unit_dirs=(
|
||||
["Dart"]="$repo_root/dart"
|
||||
["Go"]="$repo_root/go"
|
||||
["Kotlin"]="$repo_root/kotlin"
|
||||
["Python"]="$repo_root/python"
|
||||
["TypeScript"]="$repo_root/typescript"
|
||||
)
|
||||
declare -A unit_result
|
||||
declare -A unit_status
|
||||
declare -A unit_summary
|
||||
|
||||
cross_cases=(
|
||||
"Go|Dart|$repo_root/go|go run ./crosstest/go_dart.go"
|
||||
"Go|Kotlin|$repo_root/go|go run ./crosstest/go_kotlin.go"
|
||||
"Go|Python|$repo_root/go|go run ./crosstest/go_python.go"
|
||||
"Go|TypeScript|$repo_root/go|go run ./crosstest/go_typescript.go"
|
||||
"Dart|Go|$repo_root/dart|dart run crosstest/dart_go.dart"
|
||||
"Dart|Kotlin|$repo_root/dart|dart run crosstest/dart_kotlin.dart"
|
||||
"Dart|Python|$repo_root/dart|dart run crosstest/dart_python.dart"
|
||||
"Dart|TypeScript|$repo_root/dart|dart run crosstest/dart_typescript.dart"
|
||||
"Kotlin|Dart|$repo_root/kotlin|./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt"
|
||||
"Kotlin|Go|$repo_root/kotlin|./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt"
|
||||
"Kotlin|Python|$repo_root/kotlin|./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt"
|
||||
"Kotlin|TypeScript|$repo_root/kotlin|./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt"
|
||||
"Python|Dart|$repo_root/python|python3 crosstest/python_dart.py"
|
||||
"Python|Go|$repo_root/python|python3 crosstest/python_go.py"
|
||||
"Python|Kotlin|$repo_root/python|python3 crosstest/python_kotlin.py"
|
||||
"Python|TypeScript|$repo_root/python|python3 crosstest/python_typescript.py"
|
||||
"TypeScript|Dart|$repo_root/typescript|./node_modules/.bin/tsx crosstest/typescript_dart.ts"
|
||||
"TypeScript|Go|$repo_root/typescript|./node_modules/.bin/tsx crosstest/typescript_go.ts"
|
||||
"TypeScript|Kotlin|$repo_root/typescript|./node_modules/.bin/tsx crosstest/typescript_kotlin.ts"
|
||||
"TypeScript|Python|$repo_root/typescript|./node_modules/.bin/tsx crosstest/typescript_python.ts"
|
||||
)
|
||||
declare -A cross_result
|
||||
declare -A cross_status
|
||||
declare -A cross_pass_lines
|
||||
declare -A cross_fail_lines
|
||||
declare -A cross_cmds
|
||||
|
||||
expected_cross_pass_lines=16
|
||||
|
||||
count_lines() {
|
||||
local pattern="$1"
|
||||
local file="$2"
|
||||
local count
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
count="$(rg -c "$pattern" "$file" 2>/dev/null || true)"
|
||||
else
|
||||
count="$(grep -Ec "$pattern" "$file" 2>/dev/null || true)"
|
||||
fi
|
||||
echo "${count:-0}"
|
||||
}
|
||||
|
||||
run_logged() {
|
||||
local name="$1"
|
||||
local dir="$2"
|
||||
local cmd="$3"
|
||||
local log_file="$4"
|
||||
|
||||
printf 'RUN %s\n' "$name" >&2
|
||||
(
|
||||
cd "$dir" &&
|
||||
bash -lc "$cmd"
|
||||
) >"$log_file" 2>&1
|
||||
}
|
||||
|
||||
run_unit_tests() {
|
||||
local lang cmd dir log_file status
|
||||
for lang in "${languages[@]}"; do
|
||||
cmd="${unit_cmds[$lang]}"
|
||||
dir="${unit_dirs[$lang]}"
|
||||
log_file="$log_dir/unit_${lang}.log"
|
||||
run_logged "unit $lang: $cmd" "$dir" "$cmd" "$log_file"
|
||||
status=$?
|
||||
unit_status[$lang]="$status"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
unit_result[$lang]="PASS"
|
||||
else
|
||||
unit_result[$lang]="FAIL"
|
||||
fi
|
||||
unit_summary[$lang]="$(tail -n 1 "$log_file" | tr '\n' ' ')"
|
||||
done
|
||||
}
|
||||
|
||||
run_cross_tests() {
|
||||
local item server client dir cmd key log_file status pass_count fail_count
|
||||
for item in "${cross_cases[@]}"; do
|
||||
IFS='|' read -r server client dir cmd <<<"$item"
|
||||
key="$server|$client"
|
||||
log_file="$log_dir/cross_${server}_${client}.log"
|
||||
cross_cmds[$key]="(cd ${dir#$repo_root/} && $cmd)"
|
||||
run_logged "cross $server->$client: $cmd" "$dir" "$cmd" "$log_file"
|
||||
status=$?
|
||||
pass_count="$(count_lines '^PASS scenario=' "$log_file")"
|
||||
fail_count="$(count_lines '^FAIL ' "$log_file")"
|
||||
cross_status[$key]="$status"
|
||||
cross_pass_lines[$key]="$pass_count"
|
||||
cross_fail_lines[$key]="$fail_count"
|
||||
if [ "$status" -eq 0 ] && [ "$fail_count" -eq 0 ] && [ "$pass_count" -eq "$expected_cross_pass_lines" ]; then
|
||||
cross_result[$key]="PASS"
|
||||
else
|
||||
cross_result[$key]="FAIL"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
print_unit_table() {
|
||||
local lang
|
||||
printf '\n**동일언어**\n'
|
||||
printf '| 언어 | 명령 | 결과 |\n'
|
||||
printf '|---|---|---|\n'
|
||||
for lang in "${languages[@]}"; do
|
||||
printf '| %s | `%s` | %s |\n' "$lang" "${unit_cmds[$lang]}" "${unit_result[$lang]:--}"
|
||||
done
|
||||
}
|
||||
|
||||
cell_value() {
|
||||
local server="$1"
|
||||
local client="$2"
|
||||
local key="$server|$client"
|
||||
if [ "$server" = "$client" ]; then
|
||||
if [ "$mode" = "all" ] || [ "$mode" = "unit" ]; then
|
||||
echo "${unit_result[$server]:--}"
|
||||
else
|
||||
echo "-"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
echo "${cross_result[$key]:--}"
|
||||
}
|
||||
|
||||
print_cross_table() {
|
||||
local server client
|
||||
printf '\n**언어간 통신**\n'
|
||||
printf '| 서버 \\ 클라이언트 | Dart | Go | Kotlin | Python | TypeScript |\n'
|
||||
printf '|---|---|---|---|---|---|\n'
|
||||
for server in "${languages[@]}"; do
|
||||
printf '| %s' "$server"
|
||||
for client in "${languages[@]}"; do
|
||||
printf ' | %s' "$(cell_value "$server" "$client")"
|
||||
done
|
||||
printf ' |\n'
|
||||
done
|
||||
}
|
||||
|
||||
print_cross_detail_table() {
|
||||
local item server client dir cmd key
|
||||
printf '\n**크로스테스트 상세**\n'
|
||||
printf '| 방향 | 결과 | PASS scenarios | Expected | FAIL lines |\n'
|
||||
printf '|---|---:|---:|---:|---:|\n'
|
||||
for item in "${cross_cases[@]}"; do
|
||||
IFS='|' read -r server client dir cmd <<<"$item"
|
||||
key="$server|$client"
|
||||
printf '| %s -> %s | %s | %s | %s | %s |\n' \
|
||||
"$server" "$client" "${cross_result[$key]:--}" "${cross_pass_lines[$key]:--}" "$expected_cross_pass_lines" "${cross_fail_lines[$key]:--}"
|
||||
done
|
||||
}
|
||||
|
||||
print_failures() {
|
||||
local failed=0
|
||||
local lang item server client dir cmd key log_file
|
||||
|
||||
for lang in "${languages[@]}"; do
|
||||
if [ "${unit_result[$lang]:-}" = "FAIL" ]; then
|
||||
failed=1
|
||||
log_file="$log_dir/unit_${lang}.log"
|
||||
printf '\n**실패: %s 유닛/동일언어 테스트**\n' "$lang"
|
||||
printf '재현 명령: `(cd %s && %s)`\n\n' "${unit_dirs[$lang]#$repo_root/}" "${unit_cmds[$lang]}"
|
||||
tail -n 80 "$log_file"
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
|
||||
for item in "${cross_cases[@]}"; do
|
||||
IFS='|' read -r server client dir cmd <<<"$item"
|
||||
key="$server|$client"
|
||||
if [ "${cross_result[$key]:-}" = "FAIL" ]; then
|
||||
failed=1
|
||||
log_file="$log_dir/cross_${server}_${client}.log"
|
||||
printf '\n**실패: %s -> %s 크로스테스트**\n' "$server" "$client"
|
||||
printf '재현 명령: `%s`\n\n' "${cross_cmds[$key]}"
|
||||
tail -n 80 "$log_file"
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
|
||||
return "$failed"
|
||||
}
|
||||
|
||||
main() {
|
||||
case "$mode" in
|
||||
all)
|
||||
run_unit_tests
|
||||
run_cross_tests
|
||||
;;
|
||||
unit)
|
||||
run_unit_tests
|
||||
;;
|
||||
cross)
|
||||
run_cross_tests
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$mode" = "all" ] || [ "$mode" = "unit" ]; then
|
||||
print_unit_table
|
||||
fi
|
||||
if [ "$mode" = "all" ] || [ "$mode" = "cross" ]; then
|
||||
print_cross_table
|
||||
print_cross_detail_table
|
||||
fi
|
||||
|
||||
printf '\n로그 디렉터리: `%s`\n' "$log_dir"
|
||||
|
||||
if print_failures; then
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
<!-- task=01_go_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=01_go_crosstest_process_cleanup, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/01_go_crosstest_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] Harden Go subprocess runner cleanup | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `go_dart.go`, `go_python.go`, `go_kotlin.go`의 client subprocess 실행에 `WaitDelay`와 bounded pipe scanner wait를 적용한다.
|
||||
- [x] timeout/pipe scan 실패가 명확한 error message로 반환되는지 확인한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS 판정이므로 해당 없음: WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다. 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `go_typescript.go`의 패턴을 세 파일에 그대로 적용했다. `WaitDelay = 5 * time.Second`, `done` 채널 + `time.After(5 * time.Second)` select.
|
||||
- pipe scan timeout error와 context deadline exceeded error를 별도 메시지로 구분해 반환한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `go_dart.go`, `go_python.go`, `go_kotlin.go` 모두 `WaitDelay`와 bounded scanner wait를 갖는지 확인한다.
|
||||
- timeout error와 pipe scan timeout error가 구분되는지 확인한다.
|
||||
- `go_typescript.go` reference 패턴과 불필요하게 다르지 않은지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```bash
|
||||
$ cd go && go test ./...
|
||||
ok git.toki-labs.com/toki/proto-socket/go (cached)
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/dart_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/kotlin_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/python_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/typescript_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/tcp_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/ws_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/packets [no test files]
|
||||
ok git.toki-labs.com/toki/proto-socket/go/test (cached)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ cd go && go test ./...
|
||||
ok git.toki-labs.com/toki/proto-socket/go (cached)
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/dart_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/kotlin_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/python_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/typescript_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/tcp_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/ws_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/packets [no test files]
|
||||
ok git.toki-labs.com/toki/proto-socket/go/test (cached)
|
||||
|
||||
$ cd go && go run ./crosstest/go_dart.go
|
||||
INFO typeName go=TestData
|
||||
INFO typeName dart=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all go-server/dart-client crosstests passed
|
||||
exit: 0
|
||||
|
||||
$ cd go && go run ./crosstest/go_python.go
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all go-server/python-client crosstests passed
|
||||
exit: 0
|
||||
|
||||
$ cd go && go run ./crosstest/go_kotlin.go
|
||||
INFO typeName go=TestData
|
||||
[... gradle build output ...]
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from kotlin client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
BUILD SUCCESSFUL in 8s
|
||||
[... repeated for all 4 modes ...]
|
||||
PASS all go-server/kotlin-client crosstests passed
|
||||
exit: 0
|
||||
|
||||
$ rg --sort path -n "cmd\.WaitDelay|pipe scan timed out|wg\.Wait\(\)" go/crosstest/go_dart.go go/crosstest/go_python.go go/crosstest/go_kotlin.go go/crosstest/go_typescript.go
|
||||
go/crosstest/go_dart.go:385: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_dart.go:408: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_dart.go:412: return fmt.Errorf("dart client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_python.go:381: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_python.go:404: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_python.go:408: return fmt.Errorf("python client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_kotlin.go:376: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_kotlin.go:399: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_kotlin.go:403: return fmt.Errorf("kotlin client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_typescript.go:382: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_typescript.go:405: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_typescript.go:409: return fmt.Errorf("typescript client %s/%s pipe scan timed out", mode, phase)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
판정: PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 판정 | 메모 |
|
||||
|------|------|------|
|
||||
| 요구사항 충족 | PASS | `go_dart.go`, `go_python.go`, `go_kotlin.go` 모두 `cmd.WaitDelay`와 bounded pipe scanner wait를 적용했다. |
|
||||
| 동작 안정성 | PASS | 기존 `go_typescript.go` 기준 패턴과 동일하게 subprocess timeout과 pipe scan timeout을 구분한다. |
|
||||
| 범위 준수 | PASS | 계획된 세 Go crosstest runner 외 소스 변경은 없다. |
|
||||
| 검증 | PASS | `go test ./...`, 세 Go server crosstest, `rg` 확인 명령이 모두 exit code 0으로 통과했다. |
|
||||
|
||||
### Required
|
||||
|
||||
- 없음
|
||||
|
||||
### Suggested
|
||||
|
||||
- 없음
|
||||
|
||||
### Nit
|
||||
|
||||
- 없음
|
||||
|
||||
### 리뷰 검증
|
||||
|
||||
```bash
|
||||
$ cd go && go test ./...
|
||||
ok git.toki-labs.com/toki/proto-socket/go (cached)
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/dart_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/kotlin_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/python_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/crosstest/typescript_go_client [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/tcp_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/examples/ws_echo [no test files]
|
||||
? git.toki-labs.com/toki/proto-socket/go/packets [no test files]
|
||||
ok git.toki-labs.com/toki/proto-socket/go/test (cached)
|
||||
|
||||
$ cd go && go run ./crosstest/go_dart.go
|
||||
PASS all go-server/dart-client crosstests passed
|
||||
|
||||
$ cd go && go run ./crosstest/go_python.go
|
||||
PASS all go-server/python-client crosstests passed
|
||||
|
||||
$ cd go && go run ./crosstest/go_kotlin.go
|
||||
PASS all go-server/kotlin-client crosstests passed
|
||||
|
||||
$ rg --sort path -n "cmd\\.WaitDelay|pipe scan timed out|wg\\.Wait\\(\\)" go/crosstest/go_dart.go go/crosstest/go_python.go go/crosstest/go_kotlin.go go/crosstest/go_typescript.go
|
||||
go/crosstest/go_dart.go:385: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_dart.go:408: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_dart.go:412: return fmt.Errorf("dart client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_python.go:381: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_python.go:404: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_python.go:408: return fmt.Errorf("python client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_kotlin.go:376: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_kotlin.go:399: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_kotlin.go:403: return fmt.Errorf("kotlin client %s/%s pipe scan timed out", mode, phase)
|
||||
go/crosstest/go_typescript.go:382: cmd.WaitDelay = 5 * time.Second
|
||||
go/crosstest/go_typescript.go:405: go func() { wg.Wait(); close(done) }()
|
||||
go/crosstest/go_typescript.go:409: return fmt.Errorf("typescript client %s/%s pipe scan timed out", mode, phase)
|
||||
```
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Complete - 01_go_crosstest_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21
|
||||
|
||||
## 요약
|
||||
|
||||
Go server crosstest client subprocess cleanup 리뷰 완료, loop 0, final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | `WaitDelay`와 bounded pipe scanner wait 적용 확인. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `go/crosstest/go_dart.go`, `go/crosstest/go_python.go`, `go/crosstest/go_kotlin.go`의 client subprocess 실행에 `cmd.WaitDelay = 5 * time.Second`를 적용했다.
|
||||
- 각 runner의 pipe scanner wait를 5초 timeout으로 제한하고, pipe scan timeout과 process timeout error message를 구분했다.
|
||||
- PASS 판정에 따라 active task 파일을 archive 경로의 `.log` 파일로 정리했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd go && go test ./...` - PASS; Go package test 전체 통과.
|
||||
- `cd go && go run ./crosstest/go_dart.go` - PASS; `PASS all go-server/dart-client crosstests passed`.
|
||||
- `cd go && go run ./crosstest/go_python.go` - PASS; `PASS all go-server/python-client crosstests passed`.
|
||||
- `cd go && go run ./crosstest/go_kotlin.go` - PASS; `PASS all go-server/kotlin-client crosstests passed`.
|
||||
- `rg --sort path -n "cmd\\.WaitDelay|pipe scan timed out|wg\\.Wait\\(\\)" go/crosstest/go_dart.go go/crosstest/go_python.go go/crosstest/go_kotlin.go go/crosstest/go_typescript.go` - PASS; 대상 3개 파일과 TypeScript reference 모두 expected pattern 확인.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<!-- task=01_go_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# Go Crosstest Process Cleanup
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
|
||||
아래 구현 체크리스트와 리뷰 stub의 동일한 체크리스트를 끝까지 수행하고, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-cloud-G07.md`를 채운다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`도 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Go 서버 크로스테스트 중 Dart/Python/Kotlin 클라이언트 subprocess를 띄우는 러너가 timeout 이후 pipe scanner goroutine을 무제한 대기한다. TypeScript 방향 러너에는 이미 `WaitDelay`와 pipe scan timeout이 들어가 있으므로 같은 실패 모드를 Go의 나머지 클라이언트 러너에 확산한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/project/domain/go/rules.md`
|
||||
- `go/crosstest/go_dart.go`
|
||||
- `go/crosstest/go_python.go`
|
||||
- `go/crosstest/go_kotlin.go`
|
||||
- `go/crosstest/go_typescript.go`
|
||||
- `go/go.mod`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- subprocess timeout 후 pipe drain 종료: 별도 unit test 없음. 실제 `go run ./crosstest/go_*.go` 명령이 회귀 검증이다.
|
||||
- 정상 PASS scenario 수집: 기존 crosstest가 `validateResultLines`로 검증한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 관련 참조 검색: `rg --sort path -n "cmd\\.Wait\\(|wg\\.Wait\\(|cmd\\.WaitDelay|pipe scan timed out" go/crosstest`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. 이 작업 묶음은 런타임별 process API와 검증 명령이 달라 분할한다.
|
||||
|
||||
- `01_go_crosstest_process_cleanup`: Go server runner가 Go `exec.CommandContext`와 pipe scanner를 다룬다. 의존성 없음.
|
||||
- `02_dart_crosstest_process_cleanup`: Dart server runner가 Dart `Process` stream을 다룬다. 의존성 없음.
|
||||
- `03_typescript_crosstest_process_cleanup`: TypeScript server runner가 Node `ChildProcess`를 다룬다. 의존성 없음.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 계획은 `go/crosstest/go_dart.go`, `go/crosstest/go_python.go`, `go/crosstest/go_kotlin.go`만 수정한다. `go/crosstest/go_typescript.go`는 reference로만 사용하고, `agent-task/archive/**`와 client helper binaries는 건드리지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. subprocess timeout, stdout/stderr parsing, exit-status 계약을 다루는 terminal-agent 작업이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `go_dart.go`, `go_python.go`, `go_kotlin.go`의 client subprocess 실행에 `WaitDelay`와 bounded pipe scanner wait를 적용한다.
|
||||
- [ ] timeout/pipe scan 실패가 명확한 error message로 반환되는지 확인한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [TEST-1] Harden Go subprocess runner cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
- [go_dart.go](/config/workspace/proto-socket/go/crosstest/go_dart.go:405)는 `cmd.Wait()` 뒤 [go_dart.go](/config/workspace/proto-socket/go/crosstest/go_dart.go:406)의 `wg.Wait()`를 무제한 대기한다.
|
||||
- [go_python.go](/config/workspace/proto-socket/go/crosstest/go_python.go:401)와 [go_kotlin.go](/config/workspace/proto-socket/go/crosstest/go_kotlin.go:396)도 같은 구조다.
|
||||
|
||||
Before:
|
||||
|
||||
```go
|
||||
// go/crosstest/go_dart.go:405
|
||||
waitErr := cmd.Wait()
|
||||
wg.Wait()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("dart client %s/%s timed out", mode, phase)
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`go_typescript.go`의 안정화 패턴을 세 파일에 맞춰 적용한다. 각 `cmd`에 `WaitDelay = 5 * time.Second`를 설정하고, `wg.Wait()`는 channel + `time.After(5 * time.Second)`로 bounded wait한다.
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
cmd := exec.CommandContext(ctx, "dart", args...)
|
||||
cmd.Dir = dartDir
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
|
||||
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 client %s/%s pipe scan timed out", mode, phase)
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("dart client %s/%s timed out", mode, phase)
|
||||
}
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `go/crosstest/go_dart.go`: `runDartClient`에 `WaitDelay`와 pipe scan timeout 추가.
|
||||
- [ ] `go/crosstest/go_python.go`: `runPythonClient`에 같은 패턴 추가.
|
||||
- [ ] `go/crosstest/go_kotlin.go`: `runKotlinClient`에 같은 패턴 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 unit test는 작성하지 않는다. 이 파일들은 `//go:build ignore` crosstest runner이고 subprocess hang을 단위 테스트로 고립하기 어렵다. 실제 runner 명령을 회귀 검증으로 사용한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd go && go test ./...
|
||||
```
|
||||
|
||||
기대 결과: exit code 0. Go test cache 출력은 허용한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `go/crosstest/go_dart.go` | TEST-1 |
|
||||
| `go/crosstest/go_python.go` | TEST-1 |
|
||||
| `go/crosstest/go_kotlin.go` | TEST-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd go && go test ./...
|
||||
cd go && go run ./crosstest/go_dart.go
|
||||
cd go && go run ./crosstest/go_python.go
|
||||
cd go && go run ./crosstest/go_kotlin.go
|
||||
rg --sort path -n "cmd\\.WaitDelay|pipe scan timed out|wg\\.Wait\\(\\)" go/crosstest/go_dart.go go/crosstest/go_python.go go/crosstest/go_kotlin.go go/crosstest/go_typescript.go
|
||||
```
|
||||
|
||||
기대 결과: 모든 실행 명령 exit code 0. `rg` 결과에서 수정 대상 3개 파일은 `cmd.WaitDelay`와 `pipe scan timed out`을 포함하고, 무제한 `wg.Wait()`만 남지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
<!-- task=01_kotlin_subprocess_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Code Review Reference - CROSSTEST_PROCESS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=01_kotlin_subprocess_cleanup, plan=0, tag=CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/01_kotlin_subprocess_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CROSSTEST_PROCESS-1] Kotlin Dart/Go/Python client runner의 timeout process cleanup을 Kotlin TypeScript runner 수준으로 맞춘다. | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [CROSSTEST_PROCESS-1] Kotlin Dart/Go/Python client runner의 timeout process cleanup을 Kotlin TypeScript runner 수준으로 맞춘다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다. 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `label` 지역 변수를 도입해 `check` 메시지와 `validateResultLines` 첫 번째 인수를 동일한 값으로 유지했다. 계획의 지시("validateResultLines에 넘기는 문자열과 같은 값을 지역 변수로 빼서 사용")를 그대로 따른 것이다.
|
||||
- `destroyProcessTree`를 각 파일에 독립적으로 추가했다. 공유 유틸리티로 추출하면 범위를 넘으므로 TypeScript runner와 같이 파일별로 선언했다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 세 Kotlin runner가 `kotlin_typescript.kt`와 같은 tree kill + bounded stream join 패턴을 따른다.
|
||||
- timeout 후 exitCode=-1과 missing scenario 검증이 유지된다.
|
||||
- 정상 crosstest 출력이 기존 PASS line contract를 유지한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### CROSSTEST_PROCESS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ cd kotlin
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all kotlin-server/dart-client crosstests passed
|
||||
BUILD SUCCESSFUL in 14s
|
||||
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all kotlin-server/go-client crosstests passed
|
||||
BUILD SUCCESSFUL in 8s
|
||||
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all kotlin-server/python-client crosstests passed
|
||||
BUILD SUCCESSFUL in 5s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ cd kotlin
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt
|
||||
PASS all kotlin-server/dart-client crosstests passed
|
||||
BUILD SUCCESSFUL in 14s
|
||||
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt
|
||||
PASS all kotlin-server/go-client crosstests passed
|
||||
BUILD SUCCESSFUL in 8s
|
||||
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt
|
||||
PASS all kotlin-server/python-client crosstests passed
|
||||
BUILD SUCCESSFUL in 5s
|
||||
|
||||
$ ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from kotlin server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all kotlin-server/typescript-client crosstests passed
|
||||
BUILD SUCCESSFUL in 7s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 2026-05-21 리뷰 판정: PASS
|
||||
|
||||
#### 차원별 평가
|
||||
|
||||
- Correctness: PASS — Dart/Go/Python runner 모두 timeout 시 `destroyProcessTree`를 호출하고, timeout exitCode=-1 및 missing scenario 검증 계약을 유지한다.
|
||||
- Process cleanup: PASS — 세 runner 모두 TypeScript 기준 runner와 같은 process tree kill 및 bounded stream join 패턴을 따른다.
|
||||
- Scope: PASS — 변경 범위가 `kotlin/crosstest/kotlin_dart.kt`, `kotlin/crosstest/kotlin_go.kt`, `kotlin/crosstest/kotlin_python.kt`로 계획 범위와 일치한다.
|
||||
- Verification: PASS — 지정된 Kotlin crosstest 네 개를 재실행했고 모두 `BUILD SUCCESSFUL` 및 최종 PASS line을 확인했다.
|
||||
|
||||
#### Required
|
||||
|
||||
- 없음
|
||||
|
||||
#### Suggested
|
||||
|
||||
- 없음
|
||||
|
||||
#### Nit
|
||||
|
||||
- 없음
|
||||
|
||||
#### 리뷰어 검증
|
||||
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt` — PASS; `PASS all kotlin-server/dart-client crosstests passed`, `BUILD SUCCESSFUL in 12s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt` — PASS; `PASS all kotlin-server/go-client crosstests passed`, `BUILD SUCCESSFUL in 8s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt` — PASS; `PASS all kotlin-server/python-client crosstests passed`, `BUILD SUCCESSFUL in 5s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt` — PASS; `PASS all kotlin-server/typescript-client crosstests passed`, `BUILD SUCCESSFUL in 6s`.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent | Fill command output only |
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Complete - 01_kotlin_subprocess_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21
|
||||
|
||||
## 요약
|
||||
|
||||
Kotlin Dart/Go/Python client runner의 timeout process cleanup을 TypeScript runner 기준으로 맞췄고, loop 0 리뷰 결과 PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | 세 Kotlin runner가 tree kill 및 bounded stream join 패턴을 적용했고 지정 검증을 통과함. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `kotlin/crosstest/kotlin_dart.kt`, `kotlin/crosstest/kotlin_go.kt`, `kotlin/crosstest/kotlin_python.kt`에 `STREAM_JOIN_TIMEOUT_MS`와 `destroyProcessTree`를 추가했다.
|
||||
- timeout 시 direct process kill 대신 process tree kill을 수행하고, stdout/stderr reader join을 bounded join으로 변경했다.
|
||||
- stream reader가 제한 시간 안에 종료되지 않으면 label 포함 오류로 실패하도록 검증을 추가했다.
|
||||
- active plan/review 문서를 `plan_cloud_G07_0.log`, `code_review_cloud_G07_0.log`로 아카이브했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt` - PASS; `PASS all kotlin-server/dart-client crosstests passed`, `BUILD SUCCESSFUL in 12s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt` - PASS; `PASS all kotlin-server/go-client crosstests passed`, `BUILD SUCCESSFUL in 8s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt` - PASS; `PASS all kotlin-server/python-client crosstests passed`, `BUILD SUCCESSFUL in 5s`.
|
||||
- `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt` - PASS; `PASS all kotlin-server/typescript-client crosstests passed`, `BUILD SUCCESSFUL in 6s`.
|
||||
- `git diff --check -- kotlin/crosstest/kotlin_dart.kt kotlin/crosstest/kotlin_go.kt kotlin/crosstest/kotlin_python.kt` - PASS; no output.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<!-- task=01_kotlin_subprocess_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Plan - CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 이 파일이 비어 있으면 작업은 완료가 아니다.**
|
||||
구현 체크리스트를 기준으로 작업하고, 계획과 리뷰 stub의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 실제 출력을 리뷰 파일에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게`에 있는 archive 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Kotlin 서버 crosstest 중 TypeScript client runner는 timeout 시 하위 프로세스 정리와 stream join timeout을 갖고 있다. Dart/Go/Python client runner는 같은 Kotlin process orchestration 구조를 쓰지만 `destroyForcibly()`와 무제한 `join()`만 사용한다. timeout 또는 pipe 종료 실패 시 Kotlin orchestration이 멈출 수 있으므로 같은 정리 패턴으로 맞춘다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `kotlin/crosstest/kotlin_dart.kt`
|
||||
- `kotlin/crosstest/kotlin_go.kt`
|
||||
- `kotlin/crosstest/kotlin_python.kt`
|
||||
- `kotlin/crosstest/kotlin_typescript.kt`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- timeout 경로의 process tree/stream reader 정리: 기존 정상 crosstest는 성공 경로만 확인한다. 강제로 child process를 멈추게 하는 회귀 테스트는 없다.
|
||||
- 정상 경로의 PASS line parsing: 기존 `./gradlew run -PmainClass=...` crosstest로 확인 가능하다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 참고 심볼: `destroyProcessTree`는 `kotlin/crosstest/kotlin_typescript.kt:294`에만 있고, `STREAM_JOIN_TIMEOUT_MS`는 `kotlin/crosstest/kotlin_typescript.kt:42`에만 있다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
split decision policy를 먼저 평가했다. 이 task는 Kotlin 도메인의 세 runner만 다루며, 동일한 process-control 패턴을 같은 파일 구조에 적용하는 하나의 기계적 변경이다. Dart/Go/Python orchestrator 변경은 언어별 process API와 검증 방식이 달라 별도 sibling task로 분리했다.
|
||||
|
||||
Sibling task:
|
||||
|
||||
- `01_kotlin_subprocess_cleanup`: Kotlin server가 Dart/Go/Python client를 실행하는 runner 정리.
|
||||
- `02_dart_typescript_process_cleanup`: Dart server가 TypeScript client를 실행하는 runner 정리.
|
||||
- `03_go_typescript_process_cleanup`: Go server가 TypeScript client를 실행하는 runner 정리.
|
||||
- `04_python_typescript_process_cleanup`: Python server가 TypeScript client를 실행하는 runner 정리.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
`kotlin/crosstest/kotlin_typescript.kt`는 이미 기준 패턴을 갖고 있으므로 검증 참조로만 사용하고 수정하지 않는다. Kotlin core implementation, protobuf schema, client helper behavior는 변경하지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. process control, stdout/stderr parsing, timeout exit-status contract를 다루는 terminal-agent 성격의 crosstest runner 작업이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [CROSSTEST_PROCESS-1] Kotlin Dart/Go/Python client runner의 timeout process cleanup을 Kotlin TypeScript runner 수준으로 맞춘다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CROSSTEST_PROCESS-1] Kotlin subprocess cleanup 통일
|
||||
|
||||
#### 문제
|
||||
|
||||
`kotlin/crosstest/kotlin_dart.kt:322`, `kotlin/crosstest/kotlin_go.kt:322`, `kotlin/crosstest/kotlin_python.kt:321`은 child timeout 후 직접 process만 kill한다. 이어서 `stdoutThread.join()`/`stderrThread.join()`을 무제한 호출한다(`kotlin/crosstest/kotlin_dart.kt:326`, `kotlin/crosstest/kotlin_go.kt:326`, `kotlin/crosstest/kotlin_python.kt:325`). TypeScript runner의 보강된 기준은 `destroyProcessTree`와 bounded join이다(`kotlin/crosstest/kotlin_typescript.kt:294`, `kotlin/crosstest/kotlin_typescript.kt:344`).
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
세 파일에 `STREAM_JOIN_TIMEOUT_MS`와 `destroyProcessTree`를 추가하고 timeout 경로 및 join을 기준 패턴과 맞춘다.
|
||||
|
||||
Before (`kotlin/crosstest/kotlin_go.kt:322`):
|
||||
|
||||
```kotlin
|
||||
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
if (!finished) {
|
||||
process.destroyForcibly()
|
||||
}
|
||||
stdoutThread.join()
|
||||
stderrThread.join()
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```kotlin
|
||||
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
if (!finished) {
|
||||
destroyProcessTree(process)
|
||||
}
|
||||
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
|
||||
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
|
||||
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
|
||||
"$label stream readers did not finish"
|
||||
}
|
||||
```
|
||||
|
||||
구현 시 `label`이 없는 파일은 `validateResultLines`에 넘기는 문자열과 같은 값을 지역 변수로 빼서 사용한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `kotlin/crosstest/kotlin_dart.kt`: `STREAM_JOIN_TIMEOUT_MS`, `destroyProcessTree`, bounded join 적용.
|
||||
- [ ] `kotlin/crosstest/kotlin_go.kt`: 같은 변경 적용.
|
||||
- [ ] `kotlin/crosstest/kotlin_python.kt`: 같은 변경 적용.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 테스트 파일은 작성하지 않는다. 변경 대상 자체가 executable crosstest runner이고, timeout 강제 회귀 테스트는 child process fixture를 새로 만들어야 하므로 이번 범위를 넘는다. 정상 경로는 각 runner의 전체 crosstest로 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
예상 결과: 세 명령 모두 `PASS all kotlin-server/...-client crosstests passed`.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `kotlin/crosstest/kotlin_dart.kt` | CROSSTEST_PROCESS-1 |
|
||||
| `kotlin/crosstest/kotlin_go.kt` | CROSSTEST_PROCESS-1 |
|
||||
| `kotlin/crosstest/kotlin_python.kt` | CROSSTEST_PROCESS-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
예상 결과: 네 명령 모두 PASS. Gradle cache 출력은 허용한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<!-- task=02_dart_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=02_dart_crosstest_process_cleanup, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/02_dart_crosstest_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] Harden Dart subprocess stream cleanup | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `dart_go.dart`, `dart_python.dart`, `dart_kotlin.dart`에 `_streamTimeout` 상수를 추가한다.
|
||||
- [ ] 세 파일의 `Future.wait([stdoutDone, stderrDone])`에 bounded timeout과 명확한 error message를 적용한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 세 Dart runner가 모두 `_streamTimeout`과 stream wait timeout을 갖는지 확인한다.
|
||||
- error message가 상대 client label을 포함해 재현 대상을 알 수 있는지 확인한다.
|
||||
- `dart_typescript.dart` reference 패턴과 불필요하게 다르지 않은지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```bash
|
||||
$ cd dart && dart test
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ cd dart && dart test
|
||||
(output)
|
||||
|
||||
$ cd dart && dart run crosstest/dart_go.dart
|
||||
(output)
|
||||
|
||||
$ cd dart && dart run crosstest/dart_python.dart
|
||||
(output)
|
||||
|
||||
$ cd dart && dart run crosstest/dart_kotlin.dart
|
||||
(output)
|
||||
|
||||
$ rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
판정: FAIL
|
||||
리뷰 일시: 2026-05-21
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 판정 | 근거 |
|
||||
|------|------|------|
|
||||
| 정확성 | FAIL | 계획된 세 Dart runner에 `_streamTimeout`과 stream wait timeout이 적용되지 않았다. |
|
||||
| 범위 준수 | FAIL | 구현 대상 파일의 요구 변경이 모두 누락되었다. |
|
||||
| 검증 | FAIL | 구현 에이전트 소유 검증 결과가 `(output)` placeholder로 남아 있다. |
|
||||
| 문서/인수인계 | FAIL | 구현 완료표, 구현 체크리스트, 계획 대비 변경 사항, 주요 설계 결정이 작성되지 않았다. |
|
||||
|
||||
### Required
|
||||
|
||||
- [FAIL] `dart/crosstest/dart_go.dart:328`, `dart/crosstest/dart_python.dart:331`, `dart/crosstest/dart_kotlin.dart:331`에서 `Future.wait([stdoutDone, stderrDone])`가 여전히 무제한 대기한다. timeout 이후 child process를 kill해도 stdout/stderr stream close를 영원히 기다릴 수 있어, 이 작업의 핵심 결함이 그대로 남는다.
|
||||
- [FAIL] `dart/crosstest/dart_go.dart:15`, `dart/crosstest/dart_python.dart:15`, `dart/crosstest/dart_kotlin.dart:15` 주변에 `_streamTimeout` 상수가 없다. reference인 `dart/crosstest/dart_typescript.dart:16`와 같은 보호 패턴을 세 파일에 추가하라는 명시 요구를 만족하지 못했다.
|
||||
- [FAIL] 원 active review 파일의 구현 완료표와 구현 체크리스트가 미체크 상태이고, `검증 결과`는 placeholder 출력으로 남아 있었다. 이 내용은 현재 `agent-task/02_dart_crosstest_process_cleanup/code_review_cloud_G07_0.log:28` 이하에 보존되어 있으며, 구현 완료의 필수 최종 단계가 수행되지 않았다.
|
||||
|
||||
### Suggested
|
||||
|
||||
- 없음.
|
||||
|
||||
### Nit
|
||||
|
||||
- 없음.
|
||||
|
||||
### 리뷰 검증
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
dart/crosstest/dart_go.dart:328: await Future.wait([stdoutDone, stderrDone]);
|
||||
dart/crosstest/dart_python.dart:331: await Future.wait([stdoutDone, stderrDone]);
|
||||
dart/crosstest/dart_kotlin.dart:331: await Future.wait([stdoutDone, stderrDone]);
|
||||
dart/crosstest/dart_typescript.dart:16:const _streamTimeout = Duration(seconds: 5);
|
||||
dart/crosstest/dart_typescript.dart:333: await Future.wait([stdoutDone, stderrDone])
|
||||
dart/crosstest/dart_typescript.dart:334: .timeout(_streamTimeout, onTimeout: () {
|
||||
dart/crosstest/dart_typescript.dart:335: throw StateError('typescript-client $mode/$phase streams did not finish');
|
||||
```
|
||||
|
||||
필수 정적 확인에서 구현 누락이 확인되어 `dart test`와 crosstest 실행은 진행하지 않았다. 다음 active plan/review에서 동일 요구를 재작업 대상으로 넘긴다.
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
<!-- task=02_dart_crosstest_process_cleanup plan=1 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST Rework
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=02_dart_crosstest_process_cleanup, plan=1, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_1.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_1.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/02_dart_crosstest_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] Harden Dart subprocess stream cleanup | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `dart_go.dart`, `dart_python.dart`, `dart_kotlin.dart`에 `_streamTimeout` 상수를 추가한다.
|
||||
- [x] 세 파일의 `Future.wait([stdoutDone, stderrDone])`에 bounded timeout과 명확한 error message를 적용한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_1.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_1.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다. 추가 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `_streamTimeout`을 5초로 설정했다. `dart_typescript.dart`와 동일한 값으로, process kill 후 스트림이 flush되는 데 충분한 시간이며 orchestrator 교착을 방지한다.
|
||||
- error message에 client label(`go-client`, `python-client`, `kotlin-client`), mode, phase를 포함시켜 실패 재현 대상을 명확히 했다.
|
||||
- `dart_typescript.dart`의 reference 패턴을 그대로 따랐다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 세 Dart runner가 모두 `_streamTimeout`과 stream wait timeout을 갖는지 확인한다.
|
||||
- error message가 상대 client label, mode, phase를 포함해 재현 대상을 알 수 있는지 확인한다.
|
||||
- `dart_typescript.dart` reference 패턴과 불필요하게 다르지 않은지 확인한다.
|
||||
- `검증 결과`에 실제 stdout/stderr가 기록되어 있는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```bash
|
||||
$ cd dart && dart test
|
||||
00:00 +0: loading test/communicator_test.dart
|
||||
00:00 +0: test/communicator_test.dart: Communicator protocol guards response typeName mismatch completes sendRequest with error
|
||||
00:00 +1: test/socket_test.dart: ProtobufServer (plain) 서버가 정상 시작된다
|
||||
...
|
||||
00:15 +45: All tests passed!
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ cd dart && dart test
|
||||
00:00 +0: loading test/communicator_test.dart
|
||||
...
|
||||
00:15 +45: All tests passed!
|
||||
|
||||
$ cd dart && dart run crosstest/dart_go.dart
|
||||
INFO typeName dart=TestData
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all dart-server/go-client crosstests passed
|
||||
|
||||
$ cd dart && dart run crosstest/dart_python.dart
|
||||
INFO typeName dart=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all dart-server/python-client crosstests passed
|
||||
|
||||
$ cd dart && dart run crosstest/dart_kotlin.dart
|
||||
INFO typeName dart=TestData
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from kotlin client
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
...
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
BUILD SUCCESSFUL in 1s
|
||||
...
|
||||
PASS all dart-server/kotlin-client crosstests passed
|
||||
|
||||
$ rg --sort path -n "_streamTimeout|streams did not finish|Future\.wait\(\[stdoutDone, stderrDone\]\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
dart/crosstest/dart_go.dart
|
||||
16:const _streamTimeout = Duration(seconds: 5);
|
||||
329: await Future.wait([stdoutDone, stderrDone])
|
||||
330: .timeout(_streamTimeout, onTimeout: () {
|
||||
331: throw StateError('go-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_python.dart
|
||||
16:const _streamTimeout = Duration(seconds: 5);
|
||||
332: await Future.wait([stdoutDone, stderrDone])
|
||||
333: .timeout(_streamTimeout, onTimeout: () {
|
||||
334: throw StateError('python-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_kotlin.dart
|
||||
16:const _streamTimeout = Duration(seconds: 5);
|
||||
332: await Future.wait([stdoutDone, stderrDone])
|
||||
333: .timeout(_streamTimeout, onTimeout: () {
|
||||
334: throw StateError('kotlin-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_typescript.dart
|
||||
16:const _streamTimeout = Duration(seconds: 5);
|
||||
333: await Future.wait([stdoutDone, stderrDone])
|
||||
334: .timeout(_streamTimeout, onTimeout: () {
|
||||
335: throw StateError('typescript-client $mode/$phase streams did not finish');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
판정: PASS
|
||||
리뷰 일시: 2026-05-21
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 판정 | 근거 |
|
||||
|------|------|------|
|
||||
| 정확성 | PASS | `dart_go.dart`, `dart_python.dart`, `dart_kotlin.dart`에 `_streamTimeout`과 bounded stream wait가 모두 적용되었다. |
|
||||
| 범위 준수 | PASS | 계획된 세 Dart crosstest runner만 수정되었고 reference 파일은 변경되지 않았다. |
|
||||
| 검증 | PASS | reviewer가 `dart test`, 세 Dart crosstest, 정적 `rg` 확인을 직접 재실행해 모두 통과를 확인했다. |
|
||||
| 문서/인수인계 | PASS | 구현 체크리스트와 주요 설계 결정이 채워져 있고, review 판정 및 검증 근거를 남겼다. |
|
||||
|
||||
### Required
|
||||
|
||||
- 없음.
|
||||
|
||||
### Suggested
|
||||
|
||||
- 없음.
|
||||
|
||||
### Nit
|
||||
|
||||
- 없음.
|
||||
|
||||
### 리뷰 검증
|
||||
|
||||
```bash
|
||||
$ cd dart && dart test
|
||||
00:15 +45: All tests passed!
|
||||
|
||||
$ cd dart && dart run crosstest/dart_go.dart
|
||||
PASS all dart-server/go-client crosstests passed
|
||||
|
||||
$ cd dart && dart run crosstest/dart_python.dart
|
||||
PASS all dart-server/python-client crosstests passed
|
||||
|
||||
$ cd dart && dart run crosstest/dart_kotlin.dart
|
||||
PASS all dart-server/kotlin-client crosstests passed
|
||||
|
||||
$ rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
dart/crosstest/dart_go.dart:16:const _streamTimeout = Duration(seconds: 5);
|
||||
dart/crosstest/dart_go.dart:329: await Future.wait([stdoutDone, stderrDone])
|
||||
dart/crosstest/dart_go.dart:330: .timeout(_streamTimeout, onTimeout: () {
|
||||
dart/crosstest/dart_go.dart:331: throw StateError('go-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_python.dart:16:const _streamTimeout = Duration(seconds: 5);
|
||||
dart/crosstest/dart_python.dart:332: await Future.wait([stdoutDone, stderrDone])
|
||||
dart/crosstest/dart_python.dart:333: .timeout(_streamTimeout, onTimeout: () {
|
||||
dart/crosstest/dart_python.dart:334: throw StateError('python-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_kotlin.dart:16:const _streamTimeout = Duration(seconds: 5);
|
||||
dart/crosstest/dart_kotlin.dart:332: await Future.wait([stdoutDone, stderrDone])
|
||||
dart/crosstest/dart_kotlin.dart:333: .timeout(_streamTimeout, onTimeout: () {
|
||||
dart/crosstest/dart_kotlin.dart:334: throw StateError('kotlin-client $mode/$phase streams did not finish');
|
||||
dart/crosstest/dart_typescript.dart:16:const _streamTimeout = Duration(seconds: 5);
|
||||
dart/crosstest/dart_typescript.dart:333: await Future.wait([stdoutDone, stderrDone])
|
||||
dart/crosstest/dart_typescript.dart:334: .timeout(_streamTimeout, onTimeout: () {
|
||||
dart/crosstest/dart_typescript.dart:335: throw StateError('typescript-client $mode/$phase streams did not finish');
|
||||
```
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Complete - 02_dart_crosstest_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21
|
||||
|
||||
## 요약
|
||||
|
||||
Dart Go/Python/Kotlin crosstest runner subprocess stream cleanup timeout 적용, loop 2, final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | 구현 누락: `_streamTimeout`과 stream wait timeout 미적용. |
|
||||
| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | PASS | 세 Dart runner에 bounded stream cleanup 적용 및 검증 통과. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `dart/crosstest/dart_go.dart`, `dart/crosstest/dart_python.dart`, `dart/crosstest/dart_kotlin.dart`에 `_streamTimeout = Duration(seconds: 5)`를 추가했다.
|
||||
- 세 runner의 `Future.wait([stdoutDone, stderrDone])`에 stream cleanup timeout과 client/mode/phase 포함 error message를 적용했다.
|
||||
- active plan/review 파일을 `plan_cloud_G07_1.log`, `code_review_cloud_G07_1.log`로 아카이브했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd dart && dart test` - PASS; `00:15 +45: All tests passed!`
|
||||
- `cd dart && dart run crosstest/dart_go.dart` - PASS; `PASS all dart-server/go-client crosstests passed`
|
||||
- `cd dart && dart run crosstest/dart_python.dart` - PASS; `PASS all dart-server/python-client crosstests passed`
|
||||
- `cd dart && dart run crosstest/dart_kotlin.dart` - PASS; `PASS all dart-server/kotlin-client crosstests passed`
|
||||
- `rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart` - PASS; 세 수정 대상과 reference 파일에서 `_streamTimeout` 및 stream timeout 메시지 확인.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<!-- task=02_dart_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# Dart Crosstest Process Cleanup
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
|
||||
아래 구현 체크리스트와 리뷰 stub의 동일한 체크리스트를 끝까지 수행하고, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-cloud-G07.md`를 채운다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`도 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Dart 서버 크로스테스트 중 Go/Python/Kotlin 클라이언트 subprocess를 띄우는 러너가 timeout 이후 stdout/stderr stream completion을 무제한 대기한다. TypeScript 클라이언트 방향에는 이미 `_streamTimeout`이 있으므로 같은 보호를 나머지 Dart 러너에 맞춘다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/project/domain/dart/rules.md`
|
||||
- `dart/crosstest/dart_go.dart`
|
||||
- `dart/crosstest/dart_python.dart`
|
||||
- `dart/crosstest/dart_kotlin.dart`
|
||||
- `dart/crosstest/dart_typescript.dart`
|
||||
- `dart/pubspec.yaml`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- timeout 후 stream drain 종료: 별도 unit test 없음. 실제 `dart run crosstest/dart_*.dart` 명령이 회귀 검증이다.
|
||||
- 정상 PASS scenario 수집: 기존 `_validateResultLines`가 검증한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 관련 참조 검색: `rg --sort path -n "Process\\.start|process\\.exitCode\\.timeout|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)|_streamTimeout" dart/crosstest`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. 이 작업은 Go/Dart/TypeScript 런타임별 subprocess API가 다르므로 분할한다.
|
||||
|
||||
- `01_go_crosstest_process_cleanup`: 의존성 없음.
|
||||
- `02_dart_crosstest_process_cleanup`: 의존성 없음.
|
||||
- `03_typescript_crosstest_process_cleanup`: 의존성 없음.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 계획은 `dart/crosstest/dart_go.dart`, `dart/crosstest/dart_python.dart`, `dart/crosstest/dart_kotlin.dart`만 수정한다. `dart/crosstest/dart_typescript.dart`는 reference로만 사용한다. `agent-task/archive/**`와 상대 언어 client helper는 제외한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. subprocess timeout, stdout/stderr stream, long-running crosstest command의 종료 계약을 다룬다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `dart_go.dart`, `dart_python.dart`, `dart_kotlin.dart`에 `_streamTimeout` 상수를 추가한다.
|
||||
- [ ] 세 파일의 `Future.wait([stdoutDone, stderrDone])`에 bounded timeout과 명확한 error message를 적용한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [TEST-1] Harden Dart subprocess stream cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
- [dart_go.dart](/config/workspace/proto-socket/dart/crosstest/dart_go.dart:324)는 timeout 시 child process만 kill하고 [dart_go.dart](/config/workspace/proto-socket/dart/crosstest/dart_go.dart:328)에서 stream 완료를 무제한 대기한다.
|
||||
- [dart_python.dart](/config/workspace/proto-socket/dart/crosstest/dart_python.dart:327)와 [dart_kotlin.dart](/config/workspace/proto-socket/dart/crosstest/dart_kotlin.dart:327)도 같은 구조다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
// dart/crosstest/dart_go.dart:324
|
||||
final code = await process.exitCode.timeout(_processTimeout, onTimeout: () {
|
||||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone]);
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`dart_typescript.dart`의 `_streamTimeout` 패턴을 세 파일에 적용한다. 각 파일 상단의 `_processTimeout` 근처에 `const _streamTimeout = Duration(seconds: 5);`를 추가하고, stream wait에 `.timeout(_streamTimeout, onTimeout: ...)`을 붙인다.
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
const _processTimeout = Duration(seconds: 20);
|
||||
const _streamTimeout = Duration(seconds: 5);
|
||||
|
||||
final code = await process.exitCode.timeout(_processTimeout, onTimeout: () {
|
||||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('go-client $mode/$phase streams did not finish');
|
||||
});
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `dart/crosstest/dart_go.dart`: `_streamTimeout`과 stream wait timeout 추가.
|
||||
- [ ] `dart/crosstest/dart_python.dart`: `_streamTimeout`과 stream wait timeout 추가.
|
||||
- [ ] `dart/crosstest/dart_kotlin.dart`: `_streamTimeout`과 stream wait timeout 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 unit test는 작성하지 않는다. 이 파일들은 cross-language runner이고 timeout failure는 실제 subprocess 명령에서 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd dart && dart test
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `dart/crosstest/dart_go.dart` | TEST-1 |
|
||||
| `dart/crosstest/dart_python.dart` | TEST-1 |
|
||||
| `dart/crosstest/dart_kotlin.dart` | TEST-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd dart && dart test
|
||||
cd dart && dart run crosstest/dart_go.dart
|
||||
cd dart && dart run crosstest/dart_python.dart
|
||||
cd dart && dart run crosstest/dart_kotlin.dart
|
||||
rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
```
|
||||
|
||||
기대 결과: 모든 실행 명령 exit code 0. `rg` 결과에서 수정 대상 3개 파일은 `_streamTimeout`과 `streams did not finish`를 포함한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<!-- task=02_dart_crosstest_process_cleanup plan=1 tag=TEST -->
|
||||
|
||||
# Dart Crosstest Process Cleanup - Rework
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 plan은 `code_review_cloud_G07_0.log`의 FAIL 판정을 반영한 재작업 plan이다. `agent-task/archive/**`는 읽지 않는다.
|
||||
|
||||
구현 완료의 마지막 단계는 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다. 리뷰 파일의 아카이브 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
Dart 서버 크로스테스트 중 Go/Python/Kotlin 클라이언트 subprocess를 띄우는 runner가 timeout 이후 stdout/stderr stream completion을 무제한 대기한다. `dart_typescript.dart`에는 이미 `_streamTimeout`과 bounded stream wait가 있으므로 같은 패턴을 나머지 세 Dart runner에 적용한다.
|
||||
|
||||
## 이전 리뷰 결과 요약
|
||||
|
||||
plan=0 리뷰 판정은 FAIL이다.
|
||||
|
||||
- `dart_go.dart`, `dart_python.dart`, `dart_kotlin.dart`에 `_streamTimeout`이 없었다.
|
||||
- 세 파일의 `Future.wait([stdoutDone, stderrDone])`가 timeout 없이 남아 있었다.
|
||||
- 구현 체크리스트와 검증 결과가 채워지지 않았다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `dart/crosstest/dart_go.dart`, `dart/crosstest/dart_python.dart`, `dart/crosstest/dart_kotlin.dart`에 `const _streamTimeout = Duration(seconds: 5);`를 추가한다.
|
||||
- [ ] 세 파일의 `Future.wait([stdoutDone, stderrDone])`에 `.timeout(_streamTimeout, onTimeout: ...)`을 적용한다.
|
||||
- [ ] timeout error message에는 상대 client label, mode, phase가 포함되어야 한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 stdout/stderr를 `CODE_REVIEW-cloud-G07.md`에 기록한다.
|
||||
- [ ] `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 모두 채운다.
|
||||
|
||||
### [TEST-1] Harden Dart subprocess stream cleanup
|
||||
|
||||
#### 수정 대상
|
||||
|
||||
- `dart/crosstest/dart_go.dart`
|
||||
- `dart/crosstest/dart_python.dart`
|
||||
- `dart/crosstest/dart_kotlin.dart`
|
||||
|
||||
#### 기준 패턴
|
||||
|
||||
`dart/crosstest/dart_typescript.dart`의 구조를 따른다.
|
||||
|
||||
```dart
|
||||
const _processTimeout = Duration(seconds: 20);
|
||||
const _streamTimeout = Duration(seconds: 5);
|
||||
|
||||
final code = await process.exitCode.timeout(_processTimeout, onTimeout: () {
|
||||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('go-client $mode/$phase streams did not finish');
|
||||
});
|
||||
```
|
||||
|
||||
각 파일의 client label은 `go-client`, `python-client`, `kotlin-client`로 맞춘다.
|
||||
|
||||
## 테스트 작성
|
||||
|
||||
별도 unit test는 작성하지 않는다. 이 파일들은 실제 subprocess를 실행하는 crosstest runner이므로 지정된 command 실행과 `rg` 정적 확인으로 검증한다.
|
||||
|
||||
## 중간 검증
|
||||
|
||||
```bash
|
||||
cd dart && dart test
|
||||
```
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd dart && dart test
|
||||
cd dart && dart run crosstest/dart_go.dart
|
||||
cd dart && dart run crosstest/dart_python.dart
|
||||
cd dart && dart run crosstest/dart_kotlin.dart
|
||||
rg --sort path -n "_streamTimeout|streams did not finish|Future\\.wait\\(\\[stdoutDone, stderrDone\\]\\)" dart/crosstest/dart_go.dart dart/crosstest/dart_python.dart dart/crosstest/dart_kotlin.dart dart/crosstest/dart_typescript.dart
|
||||
```
|
||||
|
||||
기대 결과: 모든 실행 명령 exit code 0. `rg` 결과에서 수정 대상 세 파일과 reference 파일 모두 `_streamTimeout` 및 `streams did not finish` 패턴이 확인된다.
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!-- task=02_dart_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Code Review Reference - CROSSTEST_PROCESS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=02_dart_typescript_process_cleanup, plan=0, tag=CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/02_dart_typescript_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CROSSTEST_PROCESS-1] Dart server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout/stream wait를 bounded 처리한다. | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [CROSSTEST_PROCESS-1] Dart server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout/stream wait를 bounded 처리한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] WARN/FAIL이면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. (PASS 판정으로 해당 없음)
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다. 추가 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `_streamTimeout`을 5초로 설정했다. process kill 후 이미 닫혀 있는 스트림이 flush되는 데 충분한 시간이며, orchestrator 교착을 방지하는 데 적합하다.
|
||||
- `tsxBin` 경로는 `_typescriptDir/node_modules/.bin/tsx`로 계산한다. `_typescriptDir`은 이미 absolute path로 확정되어 있으므로 추가 검사 없이 사용한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `npx` wrapper가 제거되고 local `node_modules/.bin/tsx`가 직접 실행된다.
|
||||
- stream future가 timeout 이후 무제한 대기하지 않는다.
|
||||
- 기존 scenario PASS validation contract가 유지된다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### CROSSTEST_PROCESS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ cd dart
|
||||
$ dart run crosstest/dart_typescript.dart
|
||||
INFO typeName dart=TestData
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all dart-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ cd dart
|
||||
$ dart run crosstest/dart_typescript.dart
|
||||
INFO typeName dart=TestData
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all dart-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent | Fill command output only |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
판정: PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
- 정확성: PASS — `dart/crosstest/dart_typescript.dart`가 계획대로 local `node_modules/.bin/tsx`를 직접 실행하며 `npx` wrapper 의존을 제거했다.
|
||||
- timeout 안정성: PASS — process `exitCode` timeout 후 kill 처리에 더해 stdout/stderr stream wait에도 `_streamTimeout`이 적용되어 무제한 대기를 방지한다.
|
||||
- 범위 준수: PASS — task-owned 변경은 `dart/crosstest/dart_typescript.dart`에 한정되어 있으며, Dart/TypeScript crosstest contract는 유지된다.
|
||||
- 검증/기록: PASS — 구현 체크리스트와 검증 출력이 채워져 있고, 리뷰 중 동일 crosstest를 재실행해 PASS를 확인했다.
|
||||
|
||||
### Required
|
||||
|
||||
- 없음.
|
||||
|
||||
### Suggested
|
||||
|
||||
- 없음.
|
||||
|
||||
### Nit
|
||||
|
||||
- 없음.
|
||||
|
||||
### 리뷰어 검증
|
||||
|
||||
```bash
|
||||
$ cd dart
|
||||
$ timeout 90s dart run crosstest/dart_typescript.dart
|
||||
INFO typeName dart=TestData
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from dart server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all dart-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
```bash
|
||||
$ pgrep -af "dart_typescript_client|node_modules/.bin/tsx"
|
||||
# no output
|
||||
```
|
||||
|
||||
PASS: active review/plan 파일을 아카이브하고 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/2026/05/02_dart_typescript_process_cleanup/`로 이동한다.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Complete - 02_dart_typescript_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21
|
||||
|
||||
## 요약
|
||||
|
||||
Dart server TypeScript client runner subprocess cleanup, loop 0, final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | local `tsx` 직접 실행과 bounded stream wait 적용 확인 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `dart/crosstest/dart_typescript.dart`에서 `npx tsx` 대신 `typescript/node_modules/.bin/tsx`를 직접 실행하도록 정리했다.
|
||||
- TypeScript client process timeout 이후 stdout/stderr stream wait에 `_streamTimeout`을 적용해 orchestrator 무제한 대기를 방지했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd dart && timeout 90s dart run crosstest/dart_typescript.dart` - PASS; `PASS all dart-server/typescript-client crosstests passed`.
|
||||
- `pgrep -af "dart_typescript_client|node_modules/.bin/tsx"` - PASS; 잔여 TypeScript client/tsx 프로세스 없음.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<!-- task=02_dart_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Plan - CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 이 파일이 비어 있으면 작업은 완료가 아니다.**
|
||||
구현 체크리스트를 기준으로 작업하고, 계획과 리뷰 stub의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 실제 출력을 리뷰 파일에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게`에 있는 archive 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Dart server의 TypeScript client crosstest는 `npx tsx` wrapper를 실행하고 timeout 시 직접 process만 kill한다. `npx`가 child process를 남기거나 stream completion이 지연되면 orchestrator가 멈출 수 있다. Kotlin의 최근 보강 방향과 맞춰 직접 `node_modules/.bin/tsx`를 실행하고 stream wait도 bounded 처리한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `dart/crosstest/dart_typescript.dart`
|
||||
- `README.md`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- timeout 경로의 child cleanup과 stream completion: 별도 회귀 테스트 없음.
|
||||
- 정상 경로의 TypeScript client PASS line parsing: `dart run crosstest/dart_typescript.dart`가 TCP/WS/TLS/WSS 정상 경로를 확인한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- `_runTypescriptClient` call site: `dart/crosstest/dart_typescript.dart:119`, `:139`, `:161`, `:181`, `:225`, `:247`, `:269`, `:290`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
split decision policy를 먼저 평가했다. 이 task는 Dart 도메인의 한 runner 파일만 다룬다. Go/Python의 subprocess API는 다르고 Kotlin runner 정리도 별도 검증을 가지므로 sibling task로 분리했다.
|
||||
|
||||
Sibling task:
|
||||
|
||||
- `01_kotlin_subprocess_cleanup`: Kotlin server runner 정리.
|
||||
- `02_dart_typescript_process_cleanup`: 이 task.
|
||||
- `03_go_typescript_process_cleanup`: Go server runner 정리.
|
||||
- `04_python_typescript_process_cleanup`: Python server runner 정리.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
`dart/crosstest/dart_go.dart`, `dart/crosstest/dart_python.dart`, `dart/crosstest/dart_kotlin.dart`는 TypeScript wrapper 문제가 아니므로 제외한다. Dart core implementation과 TypeScript client helper는 변경하지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. Dart process spawning, stdout/stderr stream handling, timeout contract를 다루는 terminal-agent 작업이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [CROSSTEST_PROCESS-1] Dart server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout/stream wait를 bounded 처리한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CROSSTEST_PROCESS-1] Dart TypeScript subprocess cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
`dart/crosstest/dart_typescript.dart:298`은 `Process.start('npx', ['tsx', ...])`를 사용한다. timeout 시 `dart/crosstest/dart_typescript.dart:328`에서 exitCode timeout 후 process만 kill하고, `dart/crosstest/dart_typescript.dart:332`에서 stream future를 무제한 대기한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`_typescriptDir` 아래 `node_modules/.bin/tsx` 경로를 계산해 직접 실행한다. timeout 시 process kill 후 stdout/stderr stream future 대기는 별도 timeout으로 감싸고, stream이 닫히지 않으면 명확한 `StateError`를 발생시킨다.
|
||||
|
||||
Before (`dart/crosstest/dart_typescript.dart:298`):
|
||||
|
||||
```dart
|
||||
final process = await Process.start(
|
||||
'npx',
|
||||
[
|
||||
'tsx',
|
||||
'crosstest/dart_typescript_client.ts',
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
final tsxBin = '$_typescriptDir/node_modules/.bin/tsx';
|
||||
final process = await Process.start(
|
||||
tsxBin,
|
||||
[
|
||||
'crosstest/dart_typescript_client.ts',
|
||||
```
|
||||
|
||||
Before (`dart/crosstest/dart_typescript.dart:328`):
|
||||
|
||||
```dart
|
||||
final code = await process.exitCode.timeout(_processTimeout, onTimeout: () {
|
||||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone]);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
final code = await process.exitCode.timeout(_processTimeout, onTimeout: () {
|
||||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('typescript-client $mode/$phase streams did not finish');
|
||||
});
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `dart/crosstest/dart_typescript.dart`: `_streamTimeout` 추가.
|
||||
- [ ] `dart/crosstest/dart_typescript.dart`: `npx tsx` 대신 local `tsx` 직접 실행.
|
||||
- [ ] `dart/crosstest/dart_typescript.dart`: stdout/stderr stream future에 bounded timeout 적용.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 테스트 파일은 작성하지 않는다. 변경 대상은 crosstest executable이고, 정상 경로는 기존 full crosstest가 검증한다. timeout fixture 추가는 별도 task로 분리할 수 있다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd dart
|
||||
dart run crosstest/dart_typescript.dart
|
||||
```
|
||||
|
||||
예상 결과: `PASS all dart-server/typescript-client crosstests passed`.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `dart/crosstest/dart_typescript.dart` | CROSSTEST_PROCESS-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd dart
|
||||
dart run crosstest/dart_typescript.dart
|
||||
```
|
||||
|
||||
예상 결과: PASS.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
<!-- task=03_go_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Code Review Reference - CROSSTEST_PROCESS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=03_go_typescript_process_cleanup, plan=0, tag=CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/03_go_typescript_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CROSSTEST_PROCESS-1] Go server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 pipe wait delay를 bounded 처리한다. | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [CROSSTEST_PROCESS-1] Go server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 pipe wait delay를 bounded 처리한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] WARN/FAIL이면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. (PASS라 적용 대상 아님)
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다. 추가 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `cmd.WaitDelay = 5 * time.Second`: process exit 후 pipe close가 지연될 때 `cmd.Wait()`가 무한 대기하지 않도록 Go 1.20 `exec.Cmd.WaitDelay`를 사용했다.
|
||||
- `wg.Wait()` bounded wrap: `cmd.Wait()` 후 별도 goroutine에서 `wg.Wait()`를 실행하고 `select`로 5초 timeout을 적용했다. pipe scan goroutine이 비정상적으로 종료되지 않는 경우에도 호출자에게 명확한 error를 반환한다.
|
||||
- `tsxBin = filepath.Join(typescriptDir, "node_modules", ".bin", "tsx")`: `npx` wrapper를 제거하고 로컬 바이너리를 직접 실행해 wrapper child process 개입 없이 exit-status contract를 명확하게 했다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `npx` wrapper가 제거되고 local `node_modules/.bin/tsx`가 직접 실행된다.
|
||||
- `cmd.WaitDelay`와 bounded goroutine wait가 적용된다.
|
||||
- existing PASS/FAIL result line parsing이 유지된다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### CROSSTEST_PROCESS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ cd go
|
||||
$ go run ./crosstest/go_typescript.go
|
||||
INFO typeName go=TestData
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all go-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ cd go
|
||||
$ go run ./crosstest/go_typescript.go
|
||||
INFO typeName go=TestData
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from go server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all go-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### PASS - 2026-05-21
|
||||
|
||||
#### 차원별 평가
|
||||
|
||||
- 정확성: PASS. `npx tsx` wrapper 제거 후 `typescript/node_modules/.bin/tsx`를 직접 실행하며, 인자 순서와 PASS/FAIL line parsing contract가 유지된다.
|
||||
- 프로세스 정리: PASS. `exec.Cmd.WaitDelay`와 bounded `wg.Wait()` wrapper가 적용되어 process exit 이후 pipe scan 종료 대기가 무제한으로 남지 않는다.
|
||||
- 범위 준수: PASS. 변경은 `go/crosstest/go_typescript.go`의 TypeScript client runner에 한정되어 계획 범위를 벗어나지 않는다.
|
||||
- 검증: PASS. `cd go && go run ./crosstest/go_typescript.go`를 재실행했고 `PASS all go-server/typescript-client crosstests passed`를 확인했다.
|
||||
|
||||
#### Required
|
||||
|
||||
- 없음.
|
||||
|
||||
#### Suggested
|
||||
|
||||
- 없음.
|
||||
|
||||
#### Nit
|
||||
|
||||
- 없음.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent | Fill command output only |
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Complete - 03_go_typescript_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21T05:08:26Z
|
||||
|
||||
## 요약
|
||||
|
||||
Go server TypeScript client runner의 local `tsx` 직접 실행 및 bounded pipe wait 처리를 검토했고, 1회 루프에서 PASS로 종료했다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | 구현이 계획 범위와 일치하며 crosstest 재검증 통과 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `go/crosstest/go_typescript.go`에서 `npx tsx` wrapper 대신 `typescript/node_modules/.bin/tsx`를 직접 실행한다.
|
||||
- `exec.Cmd.WaitDelay`와 bounded pipe scan wait로 TypeScript client 종료 후 pipe cleanup 대기 시간을 제한한다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd go && go run ./crosstest/go_typescript.go` - PASS; `PASS all go-server/typescript-client crosstests passed` 확인.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
<!-- task=03_go_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Plan - CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 이 파일이 비어 있으면 작업은 완료가 아니다.**
|
||||
구현 체크리스트를 기준으로 작업하고, 계획과 리뷰 stub의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 실제 출력을 리뷰 파일에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게`에 있는 archive 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Go server의 TypeScript client crosstest는 `npx tsx` wrapper를 실행한다. timeout은 `CommandContext`로 걸려 있지만 wrapper process가 개입하고 pipe reader wait가 별도 bound 없이 `wg.Wait()`에 묶여 있다. 로컬 `tsx`를 직접 실행하고 `exec.Cmd.WaitDelay`를 사용해 timeout/pipe cleanup contract를 더 명확하게 만든다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `go/crosstest/go_typescript.go`
|
||||
- `README.md`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- timeout 경로의 wrapper child cleanup과 pipe wait delay: 별도 회귀 테스트 없음.
|
||||
- 정상 경로의 TypeScript client PASS line parsing: `go run ./crosstest/go_typescript.go`가 TCP/WS/TLS/WSS 정상 경로를 확인한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- `runTypescriptClient` call site: `go/crosstest/go_typescript.go:135`, `:169`, `:198`, `:232`, `:261`, `:295`, `:324`, `:358`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
split decision policy를 먼저 평가했다. 이 task는 Go 도메인의 한 runner 파일만 다룬다. Dart/Python의 subprocess API와 Kotlin runner 정리는 별도 검증과 다른 failure mode를 가지므로 sibling task로 분리했다.
|
||||
|
||||
Sibling task:
|
||||
|
||||
- `01_kotlin_subprocess_cleanup`: Kotlin server runner 정리.
|
||||
- `02_dart_typescript_process_cleanup`: Dart server runner 정리.
|
||||
- `03_go_typescript_process_cleanup`: 이 task.
|
||||
- `04_python_typescript_process_cleanup`: Python server runner 정리.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
`go/crosstest/go_dart.go`, `go/crosstest/go_python.go`, `go/crosstest/go_kotlin.go`는 TypeScript wrapper 문제가 아니므로 제외한다. Go core implementation과 TypeScript client helper는 변경하지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. Go subprocess orchestration, stdout/stderr parsing, timeout exit-status contract를 다루는 terminal-agent 작업이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [CROSSTEST_PROCESS-1] Go server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 pipe wait delay를 bounded 처리한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CROSSTEST_PROCESS-1] Go TypeScript subprocess cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
`go/crosstest/go_typescript.go:370`은 `args := []string{"tsx", ...}`를 만든 뒤 `go/crosstest/go_typescript.go:380`에서 `exec.CommandContext(ctx, "npx", args...)`를 실행한다. `go/crosstest/go_typescript.go:402`에서 `cmd.Wait()` 후 `go/crosstest/go_typescript.go:403`의 `wg.Wait()`는 pipe scan goroutine 종료를 무제한 기다린다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`typescriptDir/node_modules/.bin/tsx`를 직접 실행한다. `cmd.WaitDelay = 5 * time.Second`를 설정해 process exit 후 pipe close가 지연될 때 `cmd.Wait()`가 무제한 대기하지 않게 한다. `wg.Wait()`도 channel + timeout으로 감싸고, timeout이면 명확한 error를 반환한다.
|
||||
|
||||
Before (`go/crosstest/go_typescript.go:370`):
|
||||
|
||||
```go
|
||||
args := []string{
|
||||
"tsx",
|
||||
"crosstest/go_typescript_client.ts",
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
tsxBin := filepath.Join(typescriptDir, "node_modules", ".bin", "tsx")
|
||||
args := []string{
|
||||
"crosstest/go_typescript_client.ts",
|
||||
```
|
||||
|
||||
Before (`go/crosstest/go_typescript.go:380`):
|
||||
|
||||
```go
|
||||
cmd := exec.CommandContext(ctx, "npx", args...)
|
||||
cmd.Dir = typescriptDir
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
cmd := exec.CommandContext(ctx, tsxBin, args...)
|
||||
cmd.Dir = typescriptDir
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `go/crosstest/go_typescript.go`: local `tsx` 경로를 사용.
|
||||
- [ ] `go/crosstest/go_typescript.go`: `cmd.WaitDelay` 설정.
|
||||
- [ ] `go/crosstest/go_typescript.go`: `wg.Wait()`를 bounded wait로 감싸고 timeout error를 반환.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 테스트 파일은 작성하지 않는다. 이 파일은 `//go:build ignore` executable crosstest이고 정상 경로는 existing runner로 직접 검증한다. timeout fixture는 별도 도구가 필요해 이번 task 범위에서 제외한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd go
|
||||
go run ./crosstest/go_typescript.go
|
||||
```
|
||||
|
||||
예상 결과: `PASS all go-server/typescript-client crosstests passed`.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `go/crosstest/go_typescript.go` | CROSSTEST_PROCESS-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd go
|
||||
go run ./crosstest/go_typescript.go
|
||||
```
|
||||
|
||||
예상 결과: PASS. Go test cache는 해당 없는 `go run` 검증이다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
<!-- task=03_typescript_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=03_typescript_crosstest_process_cleanup, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/03_typescript_crosstest_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] Harden TypeScript child process cleanup | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] 네 TypeScript server runner의 `runClientProcess`에 process group-aware kill helper를 적용한다.
|
||||
- [x] stdout/stderr `Promise.all`에 bounded stream join timeout을 적용한다.
|
||||
- [x] `npm run check`로 TypeScript 타입 오류가 없는지 확인한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
없음. 계획과 동일하게 구현했다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `killProcessTree`는 POSIX에서 `process.kill(-child.pid, "SIGKILL")`로 process group 전체를 kill한다. Windows이거나 pid가 없으면 `child.kill("SIGKILL")`로 fallback한다.
|
||||
- `detached: process.platform !== "win32"`를 spawn에 추가해 POSIX에서 process group leader로 만든다.
|
||||
- stream join은 `withTimeout(..., STREAM_JOIN_TIMEOUT_MS, ...)` 5초로 제한한다. `withTimeout`은 이미 각 파일에 정의되어 있어 재사용했다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 네 TypeScript runner 모두 process group-aware timeout kill과 stream join timeout을 갖는지 확인한다.
|
||||
- POSIX process group kill 실패 시 direct child kill fallback이 있는지 확인한다.
|
||||
- `npm run check`가 실제 검증 결과에 포함됐는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```bash
|
||||
$ cd typescript && npm run check
|
||||
|
||||
> proto-socket@1.0.5 check
|
||||
> tsc --noEmit
|
||||
```
|
||||
|
||||
exit code 0.
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ cd typescript && npm run check && npm test
|
||||
|
||||
> proto-socket@1.0.5 check
|
||||
> tsc --noEmit
|
||||
|
||||
|
||||
> proto-socket@1.0.5 test
|
||||
> vitest run
|
||||
|
||||
|
||||
RUN v3.2.4 /config/workspace/proto-socket/typescript
|
||||
|
||||
✓ test/base_client.test.ts (7 tests) 7ms
|
||||
✓ test/communicator.test.ts (10 tests) 18ms
|
||||
✓ test/browser_ws_client.test.ts (6 tests) 18ms
|
||||
✓ test/tcp.test.ts (5 tests) 54ms
|
||||
✓ test/ws.test.ts (6 tests) 1076ms
|
||||
✓ WS > connectNodeWss rejects when peer never completes WebSocket handshake 1015ms
|
||||
|
||||
Test Files 5 passed (5)
|
||||
Tests 34 passed (34)
|
||||
Start at 17:02:02
|
||||
Duration 1.53s (transform 494ms, setup 0ms, collect 744ms, tests 1.17s, environment 1ms, prepare 530ms)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cd typescript && ./node_modules/.bin/tsx crosstest/typescript_dart.ts
|
||||
|
||||
INFO typeName ts=TestData
|
||||
INFO typeName dart=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
SERVER_RECEIVED index=101 message=fire from dart client
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName dart=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all typescript-server/dart-client crosstests passed
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cd typescript && ./node_modules/.bin/tsx crosstest/typescript_go.ts
|
||||
|
||||
INFO typeName ts=TestData
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
INFO typeName go=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from go client
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName go=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all typescript-server/go-client crosstests passed
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cd typescript && ./node_modules/.bin/tsx crosstest/typescript_python.ts
|
||||
|
||||
INFO typeName ts=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from python client
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
INFO typeName python=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all typescript-server/python-client crosstests passed
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cd typescript && ./node_modules/.bin/tsx crosstest/typescript_kotlin.ts
|
||||
|
||||
INFO typeName ts=TestData
|
||||
> Task :checkKotlinGradlePluginConfigurationErrors SKIPPED
|
||||
> Task :extractIncludeProto UP-TO-DATE
|
||||
> Task :extractProto UP-TO-DATE
|
||||
> Task :generateProto UP-TO-DATE
|
||||
> Task :compileKotlin UP-TO-DATE
|
||||
> Task :compileJava
|
||||
> Task :processResources UP-TO-DATE
|
||||
> Task :classes
|
||||
> Task :extractCrosstestProto UP-TO-DATE
|
||||
> Task :extractIncludeCrosstestProto UP-TO-DATE
|
||||
> Task :generateCrosstestProto NO-SOURCE
|
||||
> Task :compileCrosstestKotlin UP-TO-DATE
|
||||
> Task :compileCrosstestJava NO-SOURCE
|
||||
> Task :processCrosstestResources NO-SOURCE
|
||||
> Task :crosstestClasses UP-TO-DATE
|
||||
SERVER_RECEIVED index=101 message=fire from kotlin client
|
||||
|
||||
> Task :run
|
||||
INFO typeName kotlin=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from typescript server
|
||||
|
||||
BUILD SUCCESSFUL in 1s
|
||||
10 actionable tasks: 2 executed, 8 up-to-date
|
||||
[...subsequent phases all PASS...]
|
||||
PASS all typescript-server/kotlin-client crosstests passed
|
||||
```
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n "STREAM_JOIN_TIMEOUT_MS|killProcessTree|detached: process\.platform|streams did not finish|Promise\.all\(\[stdoutDone, stderrDone\]\)" typescript/crosstest/typescript_dart.ts typescript/crosstest/typescript_go.ts typescript/crosstest/typescript_python.ts typescript/crosstest/typescript_kotlin.ts
|
||||
|
||||
typescript/crosstest/typescript_dart.ts:31:const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
typescript/crosstest/typescript_dart.ts:322: detached: process.platform !== "win32",
|
||||
typescript/crosstest/typescript_dart.ts:344: killProcessTree(child);
|
||||
typescript/crosstest/typescript_dart.ts:358: Promise.all([stdoutDone, stderrDone]),
|
||||
typescript/crosstest/typescript_dart.ts:359: STREAM_JOIN_TIMEOUT_MS,
|
||||
typescript/crosstest/typescript_dart.ts:360: `${label} streams did not finish`,
|
||||
typescript/crosstest/typescript_dart.ts:408:function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
typescript/crosstest/typescript_go.ts:31:const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
typescript/crosstest/typescript_go.ts:322: detached: process.platform !== "win32",
|
||||
typescript/crosstest/typescript_go.ts:344: killProcessTree(child);
|
||||
typescript/crosstest/typescript_go.ts:358: Promise.all([stdoutDone, stderrDone]),
|
||||
typescript/crosstest/typescript_go.ts:359: STREAM_JOIN_TIMEOUT_MS,
|
||||
typescript/crosstest/typescript_go.ts:360: `${label} streams did not finish`,
|
||||
typescript/crosstest/typescript_go.ts:408:function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
typescript/crosstest/typescript_python.ts:31:const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
typescript/crosstest/typescript_python.ts:322: detached: process.platform !== "win32",
|
||||
typescript/crosstest/typescript_python.ts:344: killProcessTree(child);
|
||||
typescript/crosstest/typescript_python.ts:358: Promise.all([stdoutDone, stderrDone]),
|
||||
typescript/crosstest/typescript_python.ts:359: STREAM_JOIN_TIMEOUT_MS,
|
||||
typescript/crosstest/typescript_python.ts:360: `${label} streams did not finish`,
|
||||
typescript/crosstest/typescript_python.ts:408:function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
typescript/crosstest/typescript_kotlin.ts:31:const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
typescript/crosstest/typescript_kotlin.ts:326: detached: process.platform !== "win32",
|
||||
typescript/crosstest/typescript_kotlin.ts:348: killProcessTree(child);
|
||||
typescript/crosstest/typescript_kotlin.ts:362: Promise.all([stdoutDone, stderrDone]),
|
||||
typescript/crosstest/typescript_kotlin.ts:363: STREAM_JOIN_TIMEOUT_MS,
|
||||
typescript/crosstest/typescript_kotlin.ts:364: `${label} streams did not finish`,
|
||||
typescript/crosstest/typescript_kotlin.ts:412:function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 종합 판정
|
||||
|
||||
PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 평가 | 근거 |
|
||||
|------|------|------|
|
||||
| Correctness | Pass | 네 TypeScript crosstest runner 모두 POSIX process group kill, direct child fallback, bounded stream join을 동일하게 적용했다. |
|
||||
| Completeness | Pass | 계획과 리뷰 stub의 구현 체크리스트가 일치하고, 구현 에이전트 소유 체크리스트가 모두 완료됐다. |
|
||||
| Test coverage | Pass | 계획상 별도 unit test 없이 실제 crosstest runner와 `npm run check`, `npm test`로 검증하는 범위를 충족했다. |
|
||||
| API contract | Pass | public API 변경 없이 runner 내부 process lifecycle 처리만 변경했다. |
|
||||
| Code quality | Pass | 불필요한 debug 출력, dead code, TODO 없이 기존 파일 패턴을 유지했다. |
|
||||
| Plan deviation | Pass | 계획 범위인 `typescript/crosstest/typescript_{dart,go,python,kotlin}.ts`만 수정했다. |
|
||||
| Verification trust | Pass | 기록된 검증 명령을 재실행했고 모두 exit code 0으로 통과했다. |
|
||||
|
||||
### 발견된 문제
|
||||
|
||||
없음.
|
||||
|
||||
### 다음 단계
|
||||
|
||||
PASS: active plan/review 파일을 로그로 아카이브하고 `complete.log` 작성 후 task 디렉터리를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Complete - 03_typescript_crosstest_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21
|
||||
|
||||
## 요약
|
||||
|
||||
TypeScript crosstest runner child process cleanup hardening, loop 0, final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | 네 TypeScript crosstest runner에 process group-aware kill과 bounded stream join이 적용됐고 검증을 통과했다. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `typescript/crosstest/typescript_dart.ts`, `typescript_go.ts`, `typescript_python.ts`, `typescript_kotlin.ts`의 client subprocess timeout cleanup을 POSIX process group kill 우선, direct kill fallback 구조로 정리했다.
|
||||
- stdout/stderr stream completion 대기에 5초 timeout을 적용해 subprocess 종료 후 stream join이 무한 대기하지 않도록 했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `npm run check` - PASS; `tsc --noEmit` exit code 0.
|
||||
- `npm test` - PASS; Vitest 5 files, 34 tests passed.
|
||||
- `./node_modules/.bin/tsx crosstest/typescript_dart.ts` - PASS; all typescript-server/dart-client crosstests passed.
|
||||
- `./node_modules/.bin/tsx crosstest/typescript_go.ts` - PASS; all typescript-server/go-client crosstests passed.
|
||||
- `./node_modules/.bin/tsx crosstest/typescript_python.ts` - PASS; all typescript-server/python-client crosstests passed.
|
||||
- `./node_modules/.bin/tsx crosstest/typescript_kotlin.ts` - PASS; all typescript-server/kotlin-client crosstests passed.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<!-- task=03_typescript_crosstest_process_cleanup plan=0 tag=TEST -->
|
||||
|
||||
# TypeScript Crosstest Process Cleanup
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
|
||||
아래 구현 체크리스트와 리뷰 stub의 동일한 체크리스트를 끝까지 수행하고, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-cloud-G07.md`를 채운다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`도 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
TypeScript 서버 크로스테스트 러너는 child process timeout 시 direct child만 `SIGKILL`하고 stdout/stderr stream completion을 무제한 대기한다. Go/Dart/Python/Kotlin client command가 wrapper 또는 toolchain child를 만들 수 있으므로 process group 종료와 bounded stream wait가 필요하다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `typescript/crosstest/typescript_dart.ts`
|
||||
- `typescript/crosstest/typescript_go.ts`
|
||||
- `typescript/crosstest/typescript_python.ts`
|
||||
- `typescript/crosstest/typescript_kotlin.ts`
|
||||
- `typescript/package.json`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- Node `ChildProcess` timeout 후 descendant cleanup: 별도 unit test 없음. 실제 `tsx crosstest/typescript_*.ts` 명령이 회귀 검증이다.
|
||||
- TypeScript compile coverage: `npm run check`가 import/type 오류를 검증한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 관련 참조 검색: `rg --sort path -n "function runClientProcess|child\\.kill\\(|Promise\\.all\\(\\[stdoutDone, stderrDone\\]\\)|collectLines\\(" typescript/crosstest`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. 이 작업은 Go/Dart/TypeScript 런타임별 process-control 구현이 달라 분할한다.
|
||||
|
||||
- `01_go_crosstest_process_cleanup`: 의존성 없음.
|
||||
- `02_dart_crosstest_process_cleanup`: 의존성 없음.
|
||||
- `03_typescript_crosstest_process_cleanup`: 의존성 없음.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 계획은 `typescript/crosstest/typescript_dart.ts`, `typescript/crosstest/typescript_go.ts`, `typescript/crosstest/typescript_python.ts`, `typescript/crosstest/typescript_kotlin.ts`만 수정한다. `typescript/src/**`, 상대 언어 client helpers, `agent-task/archive/**`는 제외한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. Node child process lifecycle, stdout/stderr parsing, long-running crosstest command의 종료 계약을 다룬다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] 네 TypeScript server runner의 `runClientProcess`에 process group-aware kill helper를 적용한다.
|
||||
- [ ] stdout/stderr `Promise.all`에 bounded stream join timeout을 적용한다.
|
||||
- [ ] `npm run check`로 TypeScript 타입 오류가 없는지 확인한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [TEST-1] Harden TypeScript child process cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
- [typescript_dart.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_dart.ts:340)는 timeout 시 direct child만 kill하고 [typescript_dart.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_dart.ts:355)에서 stream completion을 무제한 대기한다.
|
||||
- [typescript_go.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_go.ts:340), [typescript_python.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_python.ts:340), [typescript_kotlin.ts](/config/workspace/proto-socket/typescript/crosstest/typescript_kotlin.ts:344)도 같은 구조다.
|
||||
|
||||
Before:
|
||||
|
||||
```ts
|
||||
// typescript/crosstest/typescript_dart.ts:339
|
||||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill("SIGKILL");
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
await Promise.all([stdoutDone, stderrDone]);
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`runClientProcess`에서 POSIX process group을 만들 수 있도록 `detached: process.platform !== "win32"`를 사용한다. timeout 시 `killProcessTree(child)` helper가 POSIX에서는 `process.kill(-child.pid, "SIGKILL")`를 먼저 시도하고 실패하면 `child.kill("SIGKILL")`로 fallback한다. stream join은 `withTimeout(Promise.all(...), STREAM_JOIN_TIMEOUT_MS, "... streams did not finish")` 형태로 제한한다.
|
||||
|
||||
After:
|
||||
|
||||
```ts
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
|
||||
const child = childProcess.spawn(command, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
killProcessTree(child);
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
await withTimeout(
|
||||
Promise.all([stdoutDone, stderrDone]),
|
||||
STREAM_JOIN_TIMEOUT_MS,
|
||||
`${label} streams did not finish`,
|
||||
);
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `typescript/crosstest/typescript_dart.ts`: process group kill helper와 stream join timeout 추가.
|
||||
- [ ] `typescript/crosstest/typescript_go.ts`: 같은 패턴 적용.
|
||||
- [ ] `typescript/crosstest/typescript_python.ts`: 같은 패턴 적용.
|
||||
- [ ] `typescript/crosstest/typescript_kotlin.ts`: 같은 패턴 적용.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 unit test는 작성하지 않는다. 이 파일들은 executable crosstest runner이고 timeout/descendant cleanup은 실제 subprocess command로 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd typescript && npm run check
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `typescript/crosstest/typescript_dart.ts` | TEST-1 |
|
||||
| `typescript/crosstest/typescript_go.ts` | TEST-1 |
|
||||
| `typescript/crosstest/typescript_python.ts` | TEST-1 |
|
||||
| `typescript/crosstest/typescript_kotlin.ts` | TEST-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd typescript && npm run check && npm test
|
||||
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_dart.ts
|
||||
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_go.ts
|
||||
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_python.ts
|
||||
cd typescript && ./node_modules/.bin/tsx crosstest/typescript_kotlin.ts
|
||||
rg --sort path -n "STREAM_JOIN_TIMEOUT_MS|killProcessTree|detached: process\\.platform|streams did not finish|Promise\\.all\\(\\[stdoutDone, stderrDone\\]\\)" typescript/crosstest/typescript_dart.ts typescript/crosstest/typescript_go.ts typescript/crosstest/typescript_python.ts typescript/crosstest/typescript_kotlin.ts
|
||||
```
|
||||
|
||||
기대 결과: 모든 실행 명령 exit code 0. `rg` 결과에서 네 파일 모두 `STREAM_JOIN_TIMEOUT_MS`, `killProcessTree`, `detached: process.platform`, `streams did not finish`를 포함한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!-- task=04_python_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Code Review Reference - CROSSTEST_PROCESS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-21
|
||||
task=04_python_typescript_process_cleanup, plan=0, tag=CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/04_python_typescript_process_cleanup/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CROSSTEST_PROCESS-1] Python server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout cleanup을 process group 기준으로 명확히 한다. | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [CROSSTEST_PROCESS-1] Python server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout cleanup을 process group 기준으로 명확히 한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일하게 구현했다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `os.killpg(process.pid, signal.SIGKILL)` 실패 시 `ProcessLookupError`만 포착하고 `process.kill()`로 fallback한다. 프로세스 그룹이 이미 종료된 경우만 해당하며, 다른 예외는 그대로 전파한다.
|
||||
- `start_new_session=True`로 새 session을 생성하여 process group ID가 subprocess의 PID와 일치하도록 한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `npx` wrapper가 제거되고 local `node_modules/.bin/tsx`가 직접 실행된다.
|
||||
- timeout cleanup이 process group kill + fallback으로 명확하다.
|
||||
- stdout/stderr capture와 PASS/FAIL result validation이 유지된다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### CROSSTEST_PROCESS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ cd python
|
||||
$ python3 crosstest/python_typescript.py
|
||||
INFO typeName python=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all python-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ cd python
|
||||
$ python3 crosstest/python_typescript.py
|
||||
INFO typeName python=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all python-server/typescript-client crosstests passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave the review-agent-only checklist unchanged.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent | Fill command output only |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
판정: PASS
|
||||
|
||||
### Findings
|
||||
|
||||
없음.
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 평가 | 근거 |
|
||||
|------|------|------|
|
||||
| Correctness | PASS | `python/crosstest/python_typescript.py`가 local `node_modules/.bin/tsx`를 직접 실행하고, timeout 시 process group kill을 수행한다. |
|
||||
| Scope | PASS | 변경 범위가 계획된 Python TypeScript crosstest runner에 한정되어 있다. |
|
||||
| Verification | PASS | `cd python && python3 crosstest/python_typescript.py` 재실행 결과 PASS를 확인했다. |
|
||||
|
||||
### 분류
|
||||
|
||||
| 분류 | 항목 |
|
||||
|------|------|
|
||||
| Required | 없음 |
|
||||
| Suggested | 없음 |
|
||||
| Nit | 없음 |
|
||||
|
||||
### 리뷰어 검증
|
||||
|
||||
```bash
|
||||
$ cd python
|
||||
$ python3 crosstest/python_typescript.py
|
||||
INFO typeName python=TestData
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
SERVER_RECEIVED index=101 message=fire from typescript client
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=1 detail=fire-and-forget sent
|
||||
PASS scenario=2 detail=received push from python server
|
||||
INFO typeName ts=TestData
|
||||
PASS scenario=3 detail=single request response matched
|
||||
PASS scenario=4 detail=concurrent request responses matched
|
||||
PASS all python-server/typescript-client crosstests passed
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Complete - 04_python_typescript_process_cleanup
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-21T05:21:52Z
|
||||
|
||||
## 요약
|
||||
|
||||
Python server TypeScript client subprocess cleanup task completed in loop 0 with final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | local `tsx` direct execution and process group timeout cleanup reviewed successfully. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- Python TypeScript crosstest runner now invokes `typescript/node_modules/.bin/tsx` directly.
|
||||
- Timeout cleanup uses a new subprocess session and process group kill path.
|
||||
- Active task plan/review files were archived as `.log` files.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd python && python3 crosstest/python_typescript.py` - PASS; `PASS all python-server/typescript-client crosstests passed`.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
<!-- task=04_python_typescript_process_cleanup plan=0 tag=CROSSTEST_PROCESS -->
|
||||
|
||||
# Plan - CROSSTEST_PROCESS
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 이 파일이 비어 있으면 작업은 완료가 아니다.**
|
||||
구현 체크리스트를 기준으로 작업하고, 계획과 리뷰 stub의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 실제 출력을 리뷰 파일에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게`에 있는 archive 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Python server의 TypeScript client crosstest는 `npx tsx` wrapper를 실행하고 timeout 시 direct process만 kill한다. wrapper child가 남거나 `communicate()` 이후 cleanup이 지연되면 실패 경로가 흐릿해질 수 있다. 로컬 `tsx`를 직접 실행하고 timeout cleanup을 명확히 한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `python/crosstest/python_typescript.py`
|
||||
- `README.md`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- timeout 경로의 process group cleanup: 별도 회귀 테스트 없음.
|
||||
- 정상 경로의 TypeScript client PASS line parsing: `python3 crosstest/python_typescript.py`가 TCP/WS/TLS/WSS 정상 경로를 확인한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- `run_typescript_client` call site: `python/crosstest/python_typescript.py:70`, `:86`, `:111`, `:127`, `:176`, `:199`, `:232`, `:256`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
split decision policy를 먼저 평가했다. 이 task는 Python 도메인의 한 runner 파일만 다룬다. Dart/Go subprocess API와 Kotlin runner 정리는 다른 언어별 위험과 검증 명령을 가지므로 sibling task로 분리했다.
|
||||
|
||||
Sibling task:
|
||||
|
||||
- `01_kotlin_subprocess_cleanup`: Kotlin server runner 정리.
|
||||
- `02_dart_typescript_process_cleanup`: Dart server runner 정리.
|
||||
- `03_go_typescript_process_cleanup`: Go server runner 정리.
|
||||
- `04_python_typescript_process_cleanup`: 이 task.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
`python/crosstest/python_dart.py`, `python/crosstest/python_go.py`, `python/crosstest/python_kotlin.py`는 TypeScript wrapper 문제가 아니므로 제외한다. Python core implementation과 TypeScript client helper는 변경하지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. Python subprocess orchestration, stdout/stderr capture, timeout cleanup contract를 다루는 terminal-agent 작업이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [CROSSTEST_PROCESS-1] Python server TypeScript client runner가 로컬 `tsx`를 직접 실행하고 timeout cleanup을 process group 기준으로 명확히 한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CROSSTEST_PROCESS-1] Python TypeScript subprocess cleanup
|
||||
|
||||
#### 문제
|
||||
|
||||
`python/crosstest/python_typescript.py:269`는 `args = ["tsx", ...]`를 만든 뒤 `python/crosstest/python_typescript.py:278`에서 `asyncio.create_subprocess_exec("npx", *args, ...)`를 실행한다. timeout 시 `python/crosstest/python_typescript.py:288`에서 direct process만 kill한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`typescript_package_dir() / "node_modules" / ".bin" / "tsx"`를 직접 실행한다. POSIX 환경에서는 `start_new_session=True`로 실행하고 timeout 시 `os.killpg(process.pid, signal.SIGKILL)`을 우선 사용하되, 실패하면 `process.kill()`로 fallback한다. 필요한 import는 `os`, `signal`을 추가한다.
|
||||
|
||||
Before (`python/crosstest/python_typescript.py:269`):
|
||||
|
||||
```python
|
||||
args = [
|
||||
"tsx",
|
||||
"crosstest/python_typescript_client.ts",
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```python
|
||||
tsx_bin = ts_dir / "node_modules" / ".bin" / "tsx"
|
||||
args = [
|
||||
str(tsx_bin),
|
||||
"crosstest/python_typescript_client.ts",
|
||||
```
|
||||
|
||||
Before (`python/crosstest/python_typescript.py:278`):
|
||||
|
||||
```python
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"npx",
|
||||
*args,
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```python
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*args,
|
||||
start_new_session=True,
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `python/crosstest/python_typescript.py`: `os`, `signal` import 추가.
|
||||
- [ ] `python/crosstest/python_typescript.py`: local `tsx` 직접 실행.
|
||||
- [ ] `python/crosstest/python_typescript.py`: timeout 시 process group kill + fallback cleanup 적용.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 테스트 파일은 작성하지 않는다. 이 파일은 executable crosstest이고 정상 경로는 existing runner로 직접 검증한다. timeout fixture는 별도 child process 도구가 필요해 이번 task 범위에서 제외한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
cd python
|
||||
python3 crosstest/python_typescript.py
|
||||
```
|
||||
|
||||
예상 결과: `PASS all python-server/typescript-client crosstests passed`.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `python/crosstest/python_typescript.py` | CROSSTEST_PROCESS-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
cd python
|
||||
python3 crosstest/python_typescript.py
|
||||
```
|
||||
|
||||
예상 결과: PASS.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -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 _goDir = Directory('$_repoRoot/go').path;
|
||||
final _certFile = '$_repoRoot/dart/test/certs/server.crt';
|
||||
|
|
@ -325,7 +326,10 @@ Future<void> _runGoClient(
|
|||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone]);
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('go-client $mode/$phase streams did not finish');
|
||||
});
|
||||
|
||||
_validateResultLines(
|
||||
'go-client $mode/$phase', code, resultLines, expectedScenarios);
|
||||
|
|
|
|||
|
|
@ -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 _kotlinDir = Directory('$_repoRoot/kotlin').path;
|
||||
final _certFile = '$_repoRoot/dart/test/certs/server.crt';
|
||||
|
|
@ -328,7 +329,10 @@ Future<void> _runKotlinClient(
|
|||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone]);
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('kotlin-client $mode/$phase streams did not finish');
|
||||
});
|
||||
|
||||
_validateResultLines(
|
||||
'kotlin-client $mode/$phase', code, resultLines, expectedScenarios);
|
||||
|
|
|
|||
|
|
@ -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 _pythonDir = Directory('$_repoRoot/python').path;
|
||||
final _certFile = '$_repoRoot/dart/test/certs/server.crt';
|
||||
|
|
@ -328,7 +329,10 @@ Future<void> _runPythonClient(
|
|||
process.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
await Future.wait([stdoutDone, stderrDone]);
|
||||
await Future.wait([stdoutDone, stderrDone])
|
||||
.timeout(_streamTimeout, onTimeout: () {
|
||||
throw StateError('python-client $mode/$phase streams did not finish');
|
||||
});
|
||||
|
||||
_validateResultLines(
|
||||
'python-client $mode/$phase', code, resultLines, expectedScenarios);
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ func runDartClient(mode string, port int, phase string, expected map[string]bool
|
|||
}
|
||||
cmd := exec.CommandContext(ctx, "dart", args...)
|
||||
cmd.Dir = dartDir
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
|
@ -403,7 +404,13 @@ func runDartClient(mode string, port int, phase string, expected map[string]bool
|
|||
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("dart client %s/%s pipe scan timed out", mode, phase)
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("dart client %s/%s timed out", mode, phase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ func runKotlinClient(mode string, port int, phase string, expected map[string]bo
|
|||
}
|
||||
cmd := exec.CommandContext(ctx, "./gradlew", "run", "--args="+clientArgs)
|
||||
cmd.Dir = kotlinDir
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
|
@ -394,7 +395,13 @@ func runKotlinClient(mode string, port int, phase string, expected map[string]bo
|
|||
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("kotlin client %s/%s pipe scan timed out", mode, phase)
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("kotlin client %s/%s timed out", mode, phase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -378,6 +378,7 @@ func runPythonClient(mode string, port int, phase string, expected map[string]bo
|
|||
}
|
||||
cmd := exec.CommandContext(ctx, "python3", args...)
|
||||
cmd.Dir = pythonDir
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
|
@ -399,7 +400,13 @@ func runPythonClient(mode string, port int, phase string, expected map[string]bo
|
|||
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("python client %s/%s pipe scan timed out", mode, phase)
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("python client %s/%s timed out", mode, phase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ Python 3.11+ implementation of the Proto Socket protocol.
|
|||
|-----------|--------|
|
||||
| TCP | Supported |
|
||||
| WebSocket | Supported |
|
||||
| TLS+TCP | Not supported yet |
|
||||
| WSS | Not supported yet |
|
||||
| TLS+TCP | Supported |
|
||||
| WSS | Supported |
|
||||
|
||||
TLS and WSS are intentionally out of scope for this first Python port.
|
||||
TLS and WSS use caller-provided `ssl.SSLContext` values.
|
||||
|
||||
## Development
|
||||
|
||||
|
|
@ -27,9 +27,11 @@ Run same-language tests:
|
|||
python -m pytest test/ -v
|
||||
```
|
||||
|
||||
Run Go/Python crosstests:
|
||||
Run cross-language crosstests:
|
||||
|
||||
```bash
|
||||
python crosstest/python_dart.py
|
||||
python crosstest/python_go.py
|
||||
python crosstest/python_kotlin.py
|
||||
python crosstest/python_typescript.py
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# TypeScript Proto Socket
|
||||
|
||||
Status: planned
|
||||
Status: available
|
||||
|
||||
This package implements Proto Socket protocol version `0.1` for TypeScript.
|
||||
|
||||
|
|
@ -48,3 +48,26 @@ npx vitest run
|
|||
cd go
|
||||
go run ./crosstest/go_typescript.go
|
||||
```
|
||||
|
||||
```bash
|
||||
cd dart
|
||||
dart run crosstest/dart_typescript.dart
|
||||
```
|
||||
|
||||
```bash
|
||||
cd kotlin
|
||||
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
|
||||
```
|
||||
|
||||
```bash
|
||||
cd python
|
||||
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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const TLS_TCP_PORT = 29806;
|
|||
const WSS_PORT = 29808;
|
||||
const WS_PATH = "/";
|
||||
const PROCESS_TIMEOUT_MS = 20_000;
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
const SERVER_OBSERVATION_MS = 200;
|
||||
|
||||
type Mode = "tcp" | "ws" | "tls" | "wss";
|
||||
|
|
@ -318,6 +319,7 @@ async function runClientProcess(
|
|||
): Promise<void> {
|
||||
const child = childProcess.spawn(command, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
|
|
@ -339,7 +341,7 @@ async function runClientProcess(
|
|||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill("SIGKILL");
|
||||
killProcessTree(child);
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
let exitCode: number;
|
||||
|
|
@ -352,7 +354,11 @@ async function runClientProcess(
|
|||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
await Promise.all([stdoutDone, stderrDone]);
|
||||
await withTimeout(
|
||||
Promise.all([stdoutDone, stderrDone]),
|
||||
STREAM_JOIN_TIMEOUT_MS,
|
||||
`${label} streams did not finish`,
|
||||
);
|
||||
if (timedOut) {
|
||||
throw new Error(`${label} timed out`);
|
||||
}
|
||||
|
|
@ -399,6 +405,18 @@ function serverTlsOptions(): { cert: Buffer; key: Buffer } {
|
|||
};
|
||||
}
|
||||
|
||||
function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
if (process.platform !== "win32" && child.pid !== undefined) {
|
||||
try {
|
||||
process.kill(-child.pid, "SIGKILL");
|
||||
return;
|
||||
} catch {
|
||||
// fallthrough to direct kill
|
||||
}
|
||||
}
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const TLS_TCP_PORT = 29810;
|
|||
const WSS_PORT = 29812;
|
||||
const WS_PATH = "/";
|
||||
const PROCESS_TIMEOUT_MS = 20_000;
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
const SERVER_OBSERVATION_MS = 200;
|
||||
|
||||
type Mode = "tcp" | "ws" | "tls" | "wss";
|
||||
|
|
@ -318,6 +319,7 @@ async function runClientProcess(
|
|||
): Promise<void> {
|
||||
const child = childProcess.spawn(command, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
|
|
@ -339,7 +341,7 @@ async function runClientProcess(
|
|||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill("SIGKILL");
|
||||
killProcessTree(child);
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
let exitCode: number;
|
||||
|
|
@ -352,7 +354,11 @@ async function runClientProcess(
|
|||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
await Promise.all([stdoutDone, stderrDone]);
|
||||
await withTimeout(
|
||||
Promise.all([stdoutDone, stderrDone]),
|
||||
STREAM_JOIN_TIMEOUT_MS,
|
||||
`${label} streams did not finish`,
|
||||
);
|
||||
if (timedOut) {
|
||||
throw new Error(`${label} timed out`);
|
||||
}
|
||||
|
|
@ -399,6 +405,18 @@ function serverTlsOptions(): { cert: Buffer; key: Buffer } {
|
|||
};
|
||||
}
|
||||
|
||||
function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
if (process.platform !== "win32" && child.pid !== undefined) {
|
||||
try {
|
||||
process.kill(-child.pid, "SIGKILL");
|
||||
return;
|
||||
} catch {
|
||||
// fallthrough to direct kill
|
||||
}
|
||||
}
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const TLS_TCP_PORT = 29814;
|
|||
const WSS_PORT = 29816;
|
||||
const WS_PATH = "/";
|
||||
const PROCESS_TIMEOUT_MS = 20_000;
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
const SERVER_OBSERVATION_MS = 200;
|
||||
|
||||
type Mode = "tcp" | "ws" | "tls" | "wss";
|
||||
|
|
@ -322,6 +323,7 @@ async function runClientProcess(
|
|||
): Promise<void> {
|
||||
const child = childProcess.spawn(command, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
|
|
@ -343,7 +345,7 @@ async function runClientProcess(
|
|||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill("SIGKILL");
|
||||
killProcessTree(child);
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
let exitCode: number;
|
||||
|
|
@ -356,7 +358,11 @@ async function runClientProcess(
|
|||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
await Promise.all([stdoutDone, stderrDone]);
|
||||
await withTimeout(
|
||||
Promise.all([stdoutDone, stderrDone]),
|
||||
STREAM_JOIN_TIMEOUT_MS,
|
||||
`${label} streams did not finish`,
|
||||
);
|
||||
if (timedOut) {
|
||||
throw new Error(`${label} timed out`);
|
||||
}
|
||||
|
|
@ -403,6 +409,18 @@ function serverTlsOptions(): { cert: Buffer; key: Buffer } {
|
|||
};
|
||||
}
|
||||
|
||||
function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
if (process.platform !== "win32" && child.pid !== undefined) {
|
||||
try {
|
||||
process.kill(-child.pid, "SIGKILL");
|
||||
return;
|
||||
} catch {
|
||||
// fallthrough to direct kill
|
||||
}
|
||||
}
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const TLS_TCP_PORT = 29818;
|
|||
const WSS_PORT = 29820;
|
||||
const WS_PATH = "/";
|
||||
const PROCESS_TIMEOUT_MS = 20_000;
|
||||
const STREAM_JOIN_TIMEOUT_MS = 5_000;
|
||||
const SERVER_OBSERVATION_MS = 200;
|
||||
|
||||
type Mode = "tcp" | "ws" | "tls" | "wss";
|
||||
|
|
@ -318,6 +319,7 @@ async function runClientProcess(
|
|||
): Promise<void> {
|
||||
const child = childProcess.spawn(command, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
|
|
@ -339,7 +341,7 @@ async function runClientProcess(
|
|||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill("SIGKILL");
|
||||
killProcessTree(child);
|
||||
}, PROCESS_TIMEOUT_MS);
|
||||
|
||||
let exitCode: number;
|
||||
|
|
@ -352,7 +354,11 @@ async function runClientProcess(
|
|||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
await Promise.all([stdoutDone, stderrDone]);
|
||||
await withTimeout(
|
||||
Promise.all([stdoutDone, stderrDone]),
|
||||
STREAM_JOIN_TIMEOUT_MS,
|
||||
`${label} streams did not finish`,
|
||||
);
|
||||
if (timedOut) {
|
||||
throw new Error(`${label} timed out`);
|
||||
}
|
||||
|
|
@ -399,6 +405,18 @@ function serverTlsOptions(): { cert: Buffer; key: Buffer } {
|
|||
};
|
||||
}
|
||||
|
||||
function killProcessTree(child: childProcess.ChildProcess): void {
|
||||
if (process.platform !== "win32" && child.pid !== undefined) {
|
||||
try {
|
||||
process.kill(-child.pid, "SIGKILL");
|
||||
return;
|
||||
} catch {
|
||||
// fallthrough to direct kill
|
||||
}
|
||||
}
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue