update execution result envelope handling
This commit is contained in:
parent
4ea01f51f0
commit
b1ace10b1f
10 changed files with 1197 additions and 29 deletions
|
|
@ -0,0 +1,167 @@
|
|||
<!-- task=execution_result_envelope/01_result_contract plan=0 tag=RESULT_CONTRACT -->
|
||||
|
||||
# Code Review Reference - RESULT_CONTRACT
|
||||
|
||||
> **[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.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-22
|
||||
task=execution_result_envelope/01_result_contract, plan=0, tag=RESULT_CONTRACT
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
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` 작성 후 active task 디렉터리를 `agent-task/archive/2026/05/execution_result_envelope/01_result_contract/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [RESULT_CONTRACT-1] BuildResult JSON Envelope | [x] |
|
||||
| [RESULT_CONTRACT-2] logEnable False Stdout Suppression | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `BuildResult`에 안정적인 execution result JSON envelope를 추가한다.
|
||||
- [x] `Application.build()`와 `Application.log()`가 `logEnable: false`에서 stdout을 오염시키지 않도록 조정한다.
|
||||
- [x] `test/oto_application_test.dart`에 BuildResult envelope와 log suppression 테스트를 추가한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다.
|
||||
- [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이면 active task 디렉터리 `agent-task/execution_result_envelope/01_result_contract/`를 `agent-task/archive/2026/05/execution_result_envelope/01_result_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/execution_result_envelope/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- Dart의 내장 `Exception("...")`의 `runtimeType.toString()` 결과가 `'Exception'`이 아닌 `'_Exception'`으로 반환되므로, `test/oto_application_test.dart`의 `BuildResult.toJson` 실패 케이스 검증에서 기대값 타입을 `'_Exception'`으로 조정하여 올바르게 검증이 수행되도록 수정했습니다. 그 외의 사항은 모두 계획과 완벽하게 일치합니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `BuildResult`에 `message` getter와 `toJson`을 추가하여 외부 자동화 파이프라인에서 신뢰하고 파싱할 수 있는 규격화된 JSON 형태(schemaVersion, type, success, exitCode, message, error)를 항상 리턴하도록 보장했습니다.
|
||||
- `Application.build()` 내 catch 절에서 `_logEnable`을 검사하여 예외 발생에 따른 빨강/노랑 콘솔 출력을 선택적으로 suppress했습니다.
|
||||
- `Application.log()` 및 `printBuildStep()` 내에서 `_logEnable` 조건에 따라 출력을 억제하여, JSON 형태의 output을 온전히 보장하기 위한 stdout 오염 방지 대책을 구현했습니다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `BuildResult.toJson()`이 문서화된 stable keys를 항상 유지하는지 확인한다.
|
||||
- `logEnable: false`가 stdout JSON 순수성을 위한 억제 수단으로 충분한지 확인한다.
|
||||
- 파일 로그 branch의 기존 error logging 동작을 깨지 않았는지 확인한다.
|
||||
- 테스트가 성공/실패 envelope와 stdout suppression을 모두 검증하는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### RESULT_CONTRACT-1 중간 검증
|
||||
```bash
|
||||
$ dart test test/oto_application_test.dart --plain-name "BuildResult.toJson"
|
||||
00:00 +0: loading test/oto_application_test.dart
|
||||
00:00 +0: BuildResult.toJson exposes success execution envelope
|
||||
00:00 +1: BuildResult.toJson exposes success execution envelope
|
||||
00:00 +1: BuildResult.toJson exposes failure execution envelope
|
||||
00:00 +2: BuildResult.toJson exposes failure execution envelope
|
||||
00:00 +2: All tests passed!
|
||||
```
|
||||
|
||||
### RESULT_CONTRACT-2 중간 검증
|
||||
```bash
|
||||
$ dart test test/oto_application_test.dart --plain-name "logEnable false"
|
||||
00:00 +0: loading test/oto_application_test.dart
|
||||
00:00 +0: logEnable false build with logEnable false suppresses failure stdout
|
||||
00:00 +1: logEnable false build with logEnable false suppresses failure stdout
|
||||
00:00 +1: logEnable ... with logEnable false suppresses command stdout logs
|
||||
00:00 +2: logEnable ... with logEnable false suppresses command stdout logs
|
||||
00:00 +2: All tests passed!
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ dart analyze
|
||||
Analyzing oto...
|
||||
No issues found!
|
||||
|
||||
$ dart test test/oto_application_test.dart
|
||||
00:01 +15: CommandExe ... handles missing file without LateInitializationError
|
||||
00:01 +16: CommandExe ... handles missing file without LateInitializationError
|
||||
00:01 +16: Application.validateYamlContent ... success contract for valid yaml
|
||||
00:01 +17: Application.validateYamlContent ... success contract for valid yaml
|
||||
00:01 +17: ... validateYamlContent returns build yaml failure contract
|
||||
00:01 +18: ... validateYamlContent returns build yaml failure contract
|
||||
00:01 +18: ... returns command list failure contract for duplicate id
|
||||
00:01 +19: ... returns command list failure contract for duplicate id
|
||||
00:01 +19: ... validateYamlContent returns pipeline failure contract
|
||||
00:01 +20: ... validateYamlContent returns pipeline failure contract
|
||||
00:01 +20: ... validateYamlContent exposes stable json shape
|
||||
00:01 +21: ... validateYamlContent exposes stable json shape
|
||||
00:01 +21: ... returns failure contract for malformed scheduler section
|
||||
00:01 +22: ... returns failure contract for malformed scheduler section
|
||||
00:01 +22: BuildResult.toJson exposes success execution envelope
|
||||
00:01 +23: BuildResult.toJson exposes success execution envelope
|
||||
00:01 +23: BuildResult.toJson exposes failure execution envelope
|
||||
00:01 +24: BuildResult.toJson exposes failure execution envelope
|
||||
00:01 +24: logEnable false build with logEnable false suppresses failure stdout
|
||||
00:01 +25: logEnable ... with logEnable false suppresses command stdout logs
|
||||
00:01 +26: logEnable ... with logEnable false suppresses command stdout logs
|
||||
00:01 +26: All tests 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 review-agent-only sections unchanged.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` -> `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` -> `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Do not remove |
|
||||
| 검증 결과 | Implementing agent | Fill actual stdout/stderr |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Pass
|
||||
- verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계: PASS - `complete.log`를 작성하고 task 디렉터리를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Complete - execution_result_envelope/01_result_contract
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-22
|
||||
|
||||
## 요약
|
||||
|
||||
BuildResult execution envelope와 logEnable=false stdout 억제 기반을 구현했고 1회 리뷰 루프에서 PASS로 종료했다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | Required/Suggested 이슈 없음 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `BuildResult`에 stable execution result JSON envelope와 message getter를 추가했다.
|
||||
- `Application.build()`, `Application.log()`, `printBuildStep()`이 `logEnable: false`에서 stdout을 오염시키지 않도록 조정했다.
|
||||
- `test/oto_application_test.dart`에 envelope shape와 stdout suppression 테스트를 추가했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `dart analyze` - PASS; `No issues found!`
|
||||
- `dart test test/oto_application_test.dart` - PASS; `All tests passed!`
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
<!-- task=execution_result_envelope/01_result_contract plan=0 tag=RESULT_CONTRACT -->
|
||||
|
||||
# Execution Result Contract Plan
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제 실행하고, 실제 출력과 구현 메모를 남긴 뒤 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 아카이브와 `complete.log` 작성은 code-review 스킬 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
마일스톤의 다음 남은 항목은 실행 후 성공/실패, exit code, 에러 정보를 외부 자동화가 파싱할 수 있는 envelope로 만드는 것이다. 현재 `BuildResult`는 성공 여부와 exit code를 들고 있지만 JSON 계약이 없다. `exe --json` CLI 작업 전에 core 결과 계약과 로그 억제 기반을 먼저 고정한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/roadmap/current.md`
|
||||
- `agent-ops/roadmap/milestones/structured-automation-surface.md`
|
||||
- `agent-ops/rules/project/domain/core/rules.md`
|
||||
- `agent-ops/rules/project/domain/cli/rules.md`
|
||||
- `lib/oto/core/build_result.dart`
|
||||
- `lib/oto/application.dart`
|
||||
- `lib/cli/cli.dart`
|
||||
- `lib/oto/commands/util/print.dart`
|
||||
- `test/oto_application_test.dart`
|
||||
- `pubspec.yaml`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `BuildResult` JSON shape: 기존 테스트는 `success`, `exitCode`, `error`만 확인하고 `toJson()` 계약을 확인하지 않는다.
|
||||
- `logEnable: false`의 stdout 억제: 기존 테스트가 없다. `exe --json`이 순수 JSON stdout을 보장하려면 실패 출력과 `Application.log()` 출력 억제를 검증해야 한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbols: none.
|
||||
- `BuildResult` 참조: `lib/oto/application.dart`, `lib/cli/commands/command_exe.dart`, `test/oto_application_test.dart`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. 공유 task group은 `execution_result_envelope`이다.
|
||||
|
||||
- `01_result_contract`: core 계약과 `logEnable` 기반을 소유한다.
|
||||
- `02+01_exe_json_cli`: `01_result_contract` 완료 후 CLI JSON 모드와 bin 스모크를 소유한다.
|
||||
|
||||
core API/foundation 변경과 CLI rollout이 분리되고 검증 방식도 달라서 split gate가 적용된다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 이 작업은 `BuildResult` JSON 계약과 `Application` 출력 억제 기반만 다룬다.
|
||||
- `CommandExe` 인자 파싱, isolate 결과 전달, actual bin smoke는 `02+01_exe_json_cli`에서 다룬다.
|
||||
- step event 계약과 pipeline event 발생 지점은 이번 task group 밖이다.
|
||||
- 새 패키지는 추가하지 않는다. `dart:convert`, `dart:async`는 SDK 라이브러리다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build: `cloud-G07`, review: `cloud-G07`.
|
||||
- stdout/exit-status 계약이 후속 CLI 자동화 검증의 기반이며, 실제 bin 출력 신뢰성에 영향을 주므로 terminal-agent 성격으로 본다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `BuildResult`에 안정적인 execution result JSON envelope를 추가한다.
|
||||
- [ ] `Application.build()`와 `Application.log()`가 `logEnable: false`에서 stdout을 오염시키지 않도록 조정한다.
|
||||
- [ ] `test/oto_application_test.dart`에 BuildResult envelope와 log suppression 테스트를 추가한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
이 하위 작업은 선행 의존성이 없다. 완료 후 `02+01_exe_json_cli`가 이 작업의 `complete.log`를 전제로 시작한다.
|
||||
|
||||
### [RESULT_CONTRACT-1] BuildResult JSON Envelope
|
||||
|
||||
#### 문제
|
||||
|
||||
[build_result.dart](/config/workspace/oto/lib/oto/core/build_result.dart:1)는 `success`, `exitCode`, `error`, `stackTrace`만 들고 있고 외부 자동화용 JSON 계약이 없다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
// lib/oto/core/build_result.dart:1
|
||||
class BuildResult {
|
||||
final bool success;
|
||||
final int exitCode;
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
const BuildResult.success()
|
||||
: success = true,
|
||||
exitCode = 0,
|
||||
error = null,
|
||||
stackTrace = null;
|
||||
|
||||
const BuildResult.failure(this.error, this.stackTrace, {this.exitCode = 10})
|
||||
: success = false;
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`BuildResult`에 `message` getter와 `toJson()`을 추가한다. schema는 다음 필드를 항상 유지한다.
|
||||
|
||||
```dart
|
||||
Map<String, dynamic> toJson() => {
|
||||
'schemaVersion': 1,
|
||||
'type': 'executionResult',
|
||||
'success': success,
|
||||
'exitCode': exitCode,
|
||||
'message': message,
|
||||
'error': success
|
||||
? null
|
||||
: {
|
||||
'type': error.runtimeType.toString(),
|
||||
'message': message,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
성공 message는 `Build completed successfully.`로 고정하고, 실패 message는 `error?.toString() ?? 'Build failed.'`를 사용한다. stack trace는 자동화 계약의 기본 JSON에 넣지 않는다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `lib/oto/core/build_result.dart`: `message` getter 추가.
|
||||
- [ ] `lib/oto/core/build_result.dart`: `toJson()` 추가.
|
||||
- [ ] `test/oto_application_test.dart`: `BuildResult` import 추가.
|
||||
- [ ] `test/oto_application_test.dart`: 성공/failure JSON shape 테스트 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `test/oto_application_test.dart`
|
||||
- 테스트명:
|
||||
- `BuildResult.toJson exposes success execution envelope`
|
||||
- `BuildResult.toJson exposes failure execution envelope`
|
||||
- 검증 목표: `schemaVersion`, `type`, `success`, `exitCode`, `message`, `error` shape가 안정적인지 확인한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
dart test test/oto_application_test.dart --plain-name "BuildResult.toJson"
|
||||
```
|
||||
|
||||
기대 결과: 추가한 `BuildResult.toJson` 테스트가 통과한다.
|
||||
|
||||
### [RESULT_CONTRACT-2] logEnable False Stdout Suppression
|
||||
|
||||
#### 문제
|
||||
|
||||
[application.dart](/config/workspace/oto/lib/oto/application.dart:153)는 `logEnable`과 무관하게 실패 정보를 stdout에 출력한다. [application.dart](/config/workspace/oto/lib/oto/application.dart:180)의 `Application.log()`도 `_logFile == null`이면 `_logEnable`을 보지 않고 `print(message)`를 호출한다. 후속 `exe --json`은 `Application.build(..., logEnable: false)`를 사용해도 stdout이 JSON 외 텍스트로 오염될 수 있다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
// lib/oto/application.dart:153
|
||||
} catch (e, stacktrace) {
|
||||
await CLI.printString(e.toString(), color: Color.redStrong);
|
||||
await CLI.printString(stacktrace.toString(), color: Color.yellowStrong);
|
||||
await printBuildStep('Build Failed', Color.redStrong);
|
||||
return BuildResult.failure(e, stacktrace);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// lib/oto/application.dart:180
|
||||
static void log(String message, {LogType logType = LogType.verbose}) {
|
||||
if (_logFile == null) {
|
||||
print(message);
|
||||
} else {
|
||||
if (_logEnable || logType == LogType.error) {
|
||||
_logFile?.writeAsStringSync('[${getDates()}] $message\n',
|
||||
mode: FileMode.append, flush: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`Application.build()`의 catch 출력은 `_logEnable`이 true일 때만 수행한다. `Application.log()`의 stdout branch도 `_logEnable`이 true일 때만 `print(message)`를 호출한다. 파일 로그 branch의 기존 error logging 동작은 유지한다.
|
||||
|
||||
```dart
|
||||
} catch (e, stacktrace) {
|
||||
if (_logEnable) {
|
||||
await CLI.printString(e.toString(), color: Color.redStrong);
|
||||
await CLI.printString(stacktrace.toString(), color: Color.yellowStrong);
|
||||
await printBuildStep('Build Failed', Color.redStrong);
|
||||
}
|
||||
return BuildResult.failure(e, stacktrace);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
if (_logFile == null) {
|
||||
if (_logEnable) {
|
||||
print(message);
|
||||
}
|
||||
} else {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `lib/oto/application.dart`: catch 출력에 `_logEnable` 조건 추가.
|
||||
- [ ] `lib/oto/application.dart`: stdout branch의 `Application.log()`가 `_logEnable`을 존중하도록 수정.
|
||||
- [ ] `test/oto_application_test.dart`: `dart:async` import 추가.
|
||||
- [ ] `test/oto_application_test.dart`: `runZoned`로 `print`를 캡처해 `logEnable: false`에서 출력이 없는지 테스트.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `test/oto_application_test.dart`
|
||||
- 테스트명:
|
||||
- `build with logEnable false suppresses failure stdout`
|
||||
- `build with logEnable false suppresses command stdout logs`
|
||||
- 검증 목표: 실패 출력과 `PrintCommand`의 `Application.log()` 출력이 `logEnable: false`에서 stdout으로 나오지 않는지 확인한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
dart test test/oto_application_test.dart --plain-name "logEnable false"
|
||||
```
|
||||
|
||||
기대 결과: `logEnable false` 관련 테스트가 통과한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `lib/oto/core/build_result.dart` | RESULT_CONTRACT-1 |
|
||||
| `lib/oto/application.dart` | RESULT_CONTRACT-2 |
|
||||
| `test/oto_application_test.dart` | RESULT_CONTRACT-1, RESULT_CONTRACT-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
dart analyze
|
||||
dart test test/oto_application_test.dart
|
||||
```
|
||||
|
||||
기대 결과: analyze issue 없이 통과하고 `test/oto_application_test.dart` 전체가 통과한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<!-- task=execution_result_envelope/02+01_exe_json_cli plan=0 tag=EXE_JSON -->
|
||||
|
||||
# Code Review Reference - EXE_JSON
|
||||
|
||||
> **[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.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-22
|
||||
task=execution_result_envelope/02+01_exe_json_cli, plan=0, tag=EXE_JSON
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
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` 작성 후 active task 디렉터리를 `agent-task/archive/2026/05/execution_result_envelope/02+01_exe_json_cli/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [EXE_JSON-1] CommandExe JSON Mode And Result Transport | [x] |
|
||||
| [EXE_JSON-2] Actual Bin Pure JSON Smoke | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `01_result_contract`의 `complete.log`가 존재하는지 확인하고 시작한다.
|
||||
- [x] `CommandExe` 인자 파싱을 `--json` 위치와 무관하게 동작하도록 정리한다.
|
||||
- [x] isolate complete 이벤트가 `BuildResult.toJson()` envelope를 전달하고 기존 scheduler 경로를 보존하도록 처리한다.
|
||||
- [x] `oto exe -f <file> --json`이 stdout에 JSON만 출력하고 process exit code를 envelope와 맞추도록 구현한다.
|
||||
- [x] `test/oto_application_test.dart`에 CommandExe JSON unit 및 actual bin smoke 테스트를 추가한다.
|
||||
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다.
|
||||
- [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이면 active task 디렉터리 `agent-task/execution_result_envelope/02+01_exe_json_cli/`를 `agent-task/archive/2026/05/execution_result_envelope/02+01_exe_json_cli/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/execution_result_envelope/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 완전히 일치하게 구현하였으며, 특이한 대안 경로나 변경 사항은 없습니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
1. **`CommandExe`의 파싱 루프 선파싱/후실행 분리**: 기존의 인자 매칭 시 즉시 실행 후 브레이크하던 흐름을, `--json` 모드 지원을 위해 유연한 `for` 루프 매치 구조로 개선하였습니다. `--json`의 위치가 앞이나 뒤 어디든 올바르게 파싱됩니다.
|
||||
2. **`isComplete()`에서 `Map`과 `int` 수신 호환**: 스케줄러 등 기존 동작을 깨뜨리지 않고, 새 JSON 모드에서는 `BuildResult.toJson()` 결과를 받아 JSON stdout 처리를 할 수 있도록 `data is Map`과 `data is int`를 모두 지원하도록 설계하였습니다.
|
||||
3. **`_printString` 생성자 주입 지원**: 테스팅 및 샌드박싱 환경에서 `stdout` 출력을 낚아채어 검증할 수 있도록 `CommandExe` 생성자에서 `_printString` 함수를 옵셔널로 주입받는 패턴을 유지하였습니다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 구현 시작 전에 `01_result_contract` 완료 근거를 확인했는지 본다.
|
||||
- `CommandExe`가 `--json` 위치와 무관하게 파싱되는지 확인한다.
|
||||
- complete 이벤트가 Map envelope와 기존 int 둘 다 처리해 scheduler 경로를 깨지 않는지 확인한다.
|
||||
- actual bin stdout이 JSON만 포함하고 exit code가 envelope와 일치하는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
각 검증 단계를 실제로 실행하여 아래와 같은 정상 패스 로그를 확인하였습니다.
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### EXE_JSON-1 중간 검증
|
||||
```bash
|
||||
$ dart test test/oto_application_test.dart --plain-name "CommandExe"
|
||||
00:00 +0: loading test/oto_application_test.dart
|
||||
00:00 +0: CommandExe ... handles missing file without LateInitializationError
|
||||
00:00 +0: CommandExe exe -f handles missing file without LateInitializationError
|
||||
There are no files in path /tmp/oto_missing_1779415634176676.yaml
|
||||
|
||||
00:00 +1: CommandExe ... handles missing file without LateInitializationError
|
||||
00:00 +1: CommandExe ... shouldPrintExecuteLog suppresses execute log for json
|
||||
00:00 +2: CommandExe ... shouldPrintExecuteLog suppresses execute log for json
|
||||
00:00 +2: CommandExe ... -f missing file --json returns execution result json
|
||||
00:00 +3: CommandExe ... -f missing file --json returns execution result json
|
||||
00:00 +3: CommandExe ... parseable execution result json and no execute logs
|
||||
00:01 +3: CommandExe ... parseable execution result json and no execute logs
|
||||
00:02 +3: CommandExe ... parseable execution result json and no execute logs
|
||||
00:03 +3: CommandExe ... parseable execution result json and no execute logs
|
||||
00:03 +4: CommandExe ... parseable execution result json and no execute logs
|
||||
00:03 +4: All tests passed!
|
||||
```
|
||||
|
||||
### EXE_JSON-2 중간 검증
|
||||
```bash
|
||||
$ dart test test/oto_application_test.dart --plain-name "actual bin exe --json"
|
||||
00:00 +0: loading test/oto_application_test.dart
|
||||
00:00 +0: CommandExe ... parseable execution result json and no execute logs
|
||||
00:01 +0: CommandExe ... parseable execution result json and no execute logs
|
||||
00:02 +0: CommandExe ... parseable execution result json and no execute logs
|
||||
00:02 +1: CommandExe ... parseable execution result json and no execute logs
|
||||
00:02 +1: All tests passed!
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ dart analyze
|
||||
Analyzing oto...
|
||||
No issues found!
|
||||
|
||||
$ dart test test/oto_application_test.dart
|
||||
00:01 +29: All tests passed!
|
||||
|
||||
$ dart test test/oto_validate_cli_test.dart test/oto_catalog_cli_test.dart test/oto_scheduler_runtime_test.dart
|
||||
00:05 +19: All tests 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 review-agent-only sections unchanged.
|
||||
|
||||
## Sections and Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` -> `[x]` only |
|
||||
| 구현 체크리스트 | Implementing agent | Check `[ ]` -> `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Do not remove |
|
||||
| 검증 결과 | Implementing agent | Fill actual stdout/stderr |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 종합 판정
|
||||
|
||||
PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 평가 | 메모 |
|
||||
|------|------|------|
|
||||
| Correctness | Pass | `--json` 위치 독립 파싱, JSON envelope 출력, exit code 전파가 실제 bin smoke와 missing-file smoke에서 확인됨. |
|
||||
| Completeness | Pass | 계획/리뷰 체크리스트가 일치하고 구현 소유 항목이 모두 완료됨. |
|
||||
| Test coverage | Pass | CommandExe unit, actual bin JSON smoke, 관련 CLI/scheduler 회귀 테스트가 있음. |
|
||||
| API contract | Pass | isolate complete 이벤트가 Map envelope와 기존 int를 모두 수신하며 scheduler 호출 경로는 보존됨. |
|
||||
| Code quality | Pass | 디버그 출력/TODO/죽은 코드는 없고, EOF 여분 빈 줄만 Nit로 기록함. |
|
||||
| Plan deviation | Pass | 선행 `01_result_contract` 변경과 현재 CLI 연결 변경이 계획된 의존 관계 안에 있음. |
|
||||
| Verification trust | Pass | 기록된 검증 명령을 재실행했고, 추가 actual bin missing-file JSON smoke도 exit code 10/envelope 일치. |
|
||||
|
||||
### 발견된 문제
|
||||
|
||||
- Nit: `lib/cli/commands/command_exe.dart:215` 파일 끝에 여분 빈 줄이 있어 `git diff --check`가 `new blank line at EOF`를 보고합니다. 마지막 빈 줄을 하나로 정리하면 됩니다.
|
||||
- Nit: `lib/oto/core/build_result.dart:34` 파일 끝에 여분 빈 줄이 있어 `git diff --check`가 `new blank line at EOF`를 보고합니다. 마지막 빈 줄을 하나로 정리하면 됩니다.
|
||||
|
||||
### 다음 단계
|
||||
|
||||
PASS: `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Complete - execution_result_envelope/02+01_exe_json_cli
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-22
|
||||
|
||||
## 요약
|
||||
|
||||
`oto exe -f <file> --json` 실행 결과 envelope 연결 작업을 1회 리뷰 루프로 완료했으며 최종 판정은 PASS입니다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | JSON stdout, process exit code, scheduler 호환 수신 경로가 검증됨. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `CommandExe` 인자 파싱을 `--json` 위치와 무관한 선파싱/후실행 구조로 정리하고, JSON 모드에서 `BuildResult.toJson()` envelope를 stdout에 출력하도록 연결함.
|
||||
- isolate complete 이벤트가 Map envelope와 기존 int를 모두 처리하도록 하여 scheduler 호출 경로를 보존함.
|
||||
- 실제 `dart run bin/main.dart exe -f <file> --json` smoke와 CommandExe JSON unit 테스트를 추가함.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `dart analyze` - PASS; `No issues found!`
|
||||
- `dart test test/oto_application_test.dart --plain-name "CommandExe"` - PASS; `All tests passed!`
|
||||
- `dart test test/oto_application_test.dart --plain-name "actual bin exe --json"` - PASS; `All tests passed!`
|
||||
- `dart test test/oto_application_test.dart` - PASS; `00:03 +29: All tests passed!`
|
||||
- `dart test test/oto_validate_cli_test.dart test/oto_catalog_cli_test.dart test/oto_scheduler_runtime_test.dart` - PASS; `00:05 +19: All tests passed!`
|
||||
- `dart run bin/main.dart exe -f <missing> --json` - PASS; process exit code 10 and JSON envelope `exitCode: 10` matched.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- `lib/cli/commands/command_exe.dart:215` EOF 여분 빈 줄 때문에 `git diff --check`가 `new blank line at EOF`를 보고함.
|
||||
- `lib/oto/core/build_result.dart:34` EOF 여분 빈 줄 때문에 `git diff --check`가 `new blank line at EOF`를 보고함.
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<!-- task=execution_result_envelope/02+01_exe_json_cli plan=0 tag=EXE_JSON -->
|
||||
|
||||
# Exe JSON CLI Plan
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제 실행하고, 실제 출력과 구현 메모를 남긴 뒤 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 아카이브와 `complete.log` 작성은 code-review 스킬 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
`BuildResult` envelope가 생기면 기존 `oto exe` 실행 결과를 외부 자동화가 파싱할 수 있는 CLI 출력으로 연결해야 한다. 현재 `CommandExe`는 exit code만 isolate complete 이벤트로 받고, `--json` 옵션이 없으며, `-f`를 만나면 즉시 실행하고 나머지 인자를 무시한다. 이 작업은 `oto exe -f <file> --json`을 안정적인 JSON stdout과 exit code 계약으로 만든다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/roadmap/current.md`
|
||||
- `agent-ops/roadmap/milestones/structured-automation-surface.md`
|
||||
- `agent-ops/rules/project/domain/cli/rules.md`
|
||||
- `agent-ops/rules/project/domain/core/rules.md`
|
||||
- `lib/cli/commands/command_exe.dart`
|
||||
- `lib/cli/commands/command_base.dart`
|
||||
- `lib/cli/commands/command_manager.dart`
|
||||
- `lib/cli/commands/command_validate.dart`
|
||||
- `lib/cli/commands/command_catalog.dart`
|
||||
- `lib/cli/cli.dart`
|
||||
- `lib/cli/commands/scheduler/scheduler_isolate.dart`
|
||||
- `lib/oto/core/build_result.dart`
|
||||
- `lib/oto/application.dart`
|
||||
- `bin/main.dart`
|
||||
- `test/oto_application_test.dart`
|
||||
- `test/oto_validate_cli_test.dart`
|
||||
- `test/oto_catalog_cli_test.dart`
|
||||
- `test/oto_scheduler_runtime_test.dart`
|
||||
- `pubspec.yaml`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `CommandExe.shouldPrintExecuteLog(['--json'])`: 기존 테스트 없음.
|
||||
- `CommandExe` direct JSON output: 기존 테스트 없음.
|
||||
- actual bin `dart run bin/main.dart exe -f <file> --json`: 기존 테스트 없음.
|
||||
- missing file JSON envelope와 exit code: 기존 테스트는 missing file이 LateInitializationError 없이 끝나는지만 확인한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbols: none.
|
||||
- `CommandExe` 참조: `bin/main.dart`, `lib/cli/commands/scheduler/scheduler_isolate.dart`, `test/oto_application_test.dart`.
|
||||
- `IsolateExe` complete event data: [command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:73)에서 int만 처리하고, [command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:138)에서 `result.exitCode`만 보낸다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. 공유 task group은 `execution_result_envelope`이다.
|
||||
|
||||
- `01_result_contract`: 선행 작업. `BuildResult.toJson()`과 `logEnable` stdout 억제 기반을 제공해야 한다.
|
||||
- `02+01_exe_json_cli`: 이 작업. 디렉터리명 기준으로 `01_result_contract`의 `complete.log`가 선행 조건이다.
|
||||
|
||||
CLI 인자 파싱, isolate 결과 전달, actual bin smoke는 core 계약과 독립 검증 단위가 다르므로 별도 plan으로 둔다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 이 작업은 `oto exe -f <file> --json`을 우선 검증 대상으로 한다.
|
||||
- `-j`, `-t` 기존 실행 경로는 깨지지 않게 유지하되 별도 bin smoke는 추가하지 않는다.
|
||||
- scheduler 내부 실행은 JSON 출력 대상이 아니므로 `executeScheduler()` 동작을 보존한다.
|
||||
- step event, log stream, artifact event는 이번 plan 밖이다.
|
||||
- 새 패키지는 추가하지 않는다. JSON encoding은 `dart:convert`를 사용한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build: `cloud-G07`, review: `cloud-G07`.
|
||||
- terminal CLI workflow, stdout parsing, exit-status contract, actual bin smoke가 성공 조건이라 `cloud-G07` 이상이 필요하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `01_result_contract`의 `complete.log`가 존재하는지 확인하고 시작한다.
|
||||
- [ ] `CommandExe` 인자 파싱을 `--json` 위치와 무관하게 동작하도록 정리한다.
|
||||
- [ ] isolate complete 이벤트가 `BuildResult.toJson()` envelope를 전달하고 기존 scheduler 경로를 보존하도록 처리한다.
|
||||
- [ ] `oto exe -f <file> --json`이 stdout에 JSON만 출력하고 process exit code를 envelope와 맞추도록 구현한다.
|
||||
- [ ] `test/oto_application_test.dart`에 CommandExe JSON unit 및 actual bin smoke 테스트를 추가한다.
|
||||
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
이 하위 작업은 `01_result_contract`에 의존한다. 구현 시작 전 `agent-task/execution_result_envelope/01_result_contract/complete.log` 또는 archive된 PASS 결과가 있어야 한다.
|
||||
|
||||
### [EXE_JSON-1] CommandExe JSON Mode And Result Transport
|
||||
|
||||
#### 문제
|
||||
|
||||
[command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:30)의 `execute()`는 인자를 순회하다가 `-j`, `-t`, `-f`를 만나면 즉시 실행 후 `break`한다. 따라서 `exe -f file.yaml --json` 같은 후행 옵션을 해석할 수 없다. 또한 [command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:73)는 complete 이벤트 data가 int인 경우만 처리하고, [command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:138)는 isolate에서 `result.exitCode`만 보낸다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
// lib/cli/commands/command_exe.dart:30
|
||||
Future execute(List<String> parameters) async {
|
||||
for (var i = 0; i < parameters.length; ++i) {
|
||||
var item = parameters[i];
|
||||
if (item.startsWith('-j')) {
|
||||
_handler = IsolateManager.create(IsolateExe(BuildType.jenkins));
|
||||
await isComplete();
|
||||
break;
|
||||
} else if (item.startsWith('-t')) {
|
||||
_handler = IsolateManager.create(IsolateExe(BuildType.test));
|
||||
await isComplete();
|
||||
break;
|
||||
} else if (item.startsWith('-f')) {
|
||||
var file = '';
|
||||
if (parameters.length > i + 1) {
|
||||
file = parameters[i + 1];
|
||||
} else {
|
||||
file = item.replaceFirst('-f', '').trimLeft();
|
||||
}
|
||||
await startYaml(file);
|
||||
break;
|
||||
} else {
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
exitCode = _exitCode;
|
||||
_handler?.exit();
|
||||
return simpleFuture;
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`execute()`를 먼저 파싱하고 나중에 실행하는 구조로 바꾼다. `--json`, `--file`, `--file=<path>`, `-f <path>`, `-fpath`를 지원하고, JSON 모드에서는 `shouldPrintExecuteLog()`가 false를 반환한다.
|
||||
|
||||
complete 이벤트는 기존 int와 새 Map envelope를 모두 처리한다.
|
||||
|
||||
```dart
|
||||
_handler!.addListener('complete', (data) {
|
||||
if (data is Map) {
|
||||
_resultJson = Map<String, dynamic>.from(data);
|
||||
_exitCode = (_resultJson!['exitCode'] as num?)?.toInt() ?? 10;
|
||||
} else if (data is int) {
|
||||
_exitCode = data;
|
||||
}
|
||||
_complete = true;
|
||||
});
|
||||
```
|
||||
|
||||
`IsolateExe.onReady()`는 `result.toJson()`을 보낸다. `executeScheduler()`는 JSON을 출력하지 않고 기존처럼 완료만 기다리도록 유지한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: `dart:convert`와 `BuildResult` import 추가.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: `_resultJson`과 optional `_printString` 추가.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: `arguments`에 `--json`, `--file` 설명 추가.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: `shouldPrintExecuteLog()` override 추가.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: 인자 파싱을 선파싱/후실행 구조로 변경.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: complete listener가 Map envelope와 int를 모두 처리하도록 변경.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: `IsolateExe.onReady()`에서 `result.toJson()` 전송.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `test/oto_application_test.dart`
|
||||
- 테스트명:
|
||||
- `CommandExe shouldPrintExecuteLog suppresses execute log for json`
|
||||
- `CommandExe exe -f missing file --json returns execution result json`
|
||||
- 검증 목표: JSON 옵션이 위치와 무관하게 인식되고, missing file에서도 JSON envelope와 exit code 10이 나온다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
dart test test/oto_application_test.dart --plain-name "CommandExe"
|
||||
```
|
||||
|
||||
기대 결과: 기존 missing file 테스트와 새 CommandExe JSON 테스트가 통과한다.
|
||||
|
||||
### [EXE_JSON-2] Actual Bin Pure JSON Smoke
|
||||
|
||||
#### 문제
|
||||
|
||||
마일스톤 완료 기준은 외부 자동화가 내부 Dart API가 아니라 CLI를 호출해 결과를 해석하는 것이다. [command_manager.dart](/config/workspace/oto/lib/cli/commands/command_manager.dart:41)는 기본적으로 `Execute command:` 로그를 출력하고, [command_validate.dart](/config/workspace/oto/lib/cli/commands/command_validate.dart:32)와 [command_catalog.dart](/config/workspace/oto/lib/cli/commands/command_catalog.dart:32)는 JSON 모드에서 이를 끄는 패턴을 이미 갖고 있다. `exe --json`도 actual bin smoke로 stdout이 parseable JSON인지 검증해야 한다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
// lib/cli/commands/command_manager.dart:41
|
||||
if (commandItem.shouldPrintExecuteLog(parameters)) {
|
||||
await CLI.println('Execute command: $command$argStr',
|
||||
color: Color.green);
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`CommandExe.shouldPrintExecuteLog()`를 `CommandCatalogCli`/`CommandValidateCli`와 같은 패턴으로 구현한다. JSON 모드 실행은 `Application.build(..., logEnable: false)` 기반으로 돌리고, 완료 후 `JsonEncoder.withIndent(' ')`로 envelope를 한 번만 stdout에 쓴다.
|
||||
|
||||
`01_result_contract`에서 `Application.log()`가 `_logEnable`을 존중해야 `PrintCommand`가 있는 YAML도 stdout을 오염시키지 않는다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: JSON 모드에서 `IsolateExe`에 `logEnable: false` 전달.
|
||||
- [ ] `lib/cli/commands/command_exe.dart`: 실행 완료 후 `_resultJson`을 stdout에 출력.
|
||||
- [ ] `test/oto_application_test.dart`: 실제 `dart run bin/main.dart exe -f <temp.yaml> --json` 테스트 추가.
|
||||
- [ ] `test/oto_application_test.dart`: stdout에 `Execute command:`, `Usage:`, YAML command output이 섞이지 않는지 확인.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `test/oto_application_test.dart`
|
||||
- 테스트명: `actual bin exe --json output has parseable execution result json and no execute logs`
|
||||
- fixture: temp YAML에 `Print` command를 넣어 `Application.log()` 억제까지 검증한다.
|
||||
- assertion: process exit code 0, stdout JSON parse 성공, `type == executionResult`, `success == true`, `exitCode == 0`, stdout에 `Execute command:`, `Usage:`, YAML message가 없음.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
dart test test/oto_application_test.dart --plain-name "actual bin exe --json"
|
||||
```
|
||||
|
||||
기대 결과: actual bin JSON smoke가 통과한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `lib/cli/commands/command_exe.dart` | EXE_JSON-1, EXE_JSON-2 |
|
||||
| `test/oto_application_test.dart` | EXE_JSON-1, EXE_JSON-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
dart analyze
|
||||
dart test test/oto_application_test.dart
|
||||
dart test test/oto_validate_cli_test.dart test/oto_catalog_cli_test.dart test/oto_scheduler_runtime_test.dart
|
||||
```
|
||||
|
||||
기대 결과: analyze issue 없이 통과하고 관련 CLI/application/scheduler 회귀 테스트가 모두 통과한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// ignore_for_file: avoid_init_to_null
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dart_framework/data/isolate_data.dart';
|
||||
|
|
@ -9,6 +10,7 @@ import 'package:oto/cli/cli.dart';
|
|||
import 'package:oto/cli/commands/command_base.dart';
|
||||
import 'package:dart_framework/utils/system_util.dart';
|
||||
import 'package:oto/oto/application.dart';
|
||||
import 'package:oto/oto/core/build_result.dart';
|
||||
import 'package:oto/oto/data/command_data.dart';
|
||||
|
||||
class CommandExe extends CommandBase {
|
||||
|
|
@ -16,41 +18,99 @@ class CommandExe extends CommandBase {
|
|||
bool _complete = false;
|
||||
bool _ready = false;
|
||||
int _exitCode = 0;
|
||||
Map<String, dynamic>? _resultJson;
|
||||
final Future<void> Function(String) _printString;
|
||||
|
||||
CommandExe({Future<void> Function(String)? printString})
|
||||
: _printString = printString ??
|
||||
((value) async {
|
||||
stdout.writeln(value);
|
||||
});
|
||||
|
||||
@override
|
||||
Map<String, List<String>> get arguments => {
|
||||
'-j': ["Used when it's a Jenkins build."],
|
||||
'-t': ["This is the setup for testing."],
|
||||
'-f {file path}': ["Run the yaml file to process."]
|
||||
'-f {file path}': ["Run the yaml file to process."],
|
||||
'--file {file path}': ["Run the yaml file to process."],
|
||||
'--json': ["Output execution result as structured JSON format."]
|
||||
};
|
||||
@override
|
||||
String get name => 'exe';
|
||||
|
||||
@override
|
||||
bool shouldPrintExecuteLog(List<String> parameters) {
|
||||
return !parameters.contains('--json');
|
||||
}
|
||||
|
||||
@override
|
||||
Future execute(List<String> parameters) async {
|
||||
for (var i = 0; i < parameters.length; ++i) {
|
||||
var item = parameters[i];
|
||||
if (item.startsWith('-j')) {
|
||||
_handler = IsolateManager.create(IsolateExe(BuildType.jenkins));
|
||||
await isComplete();
|
||||
break;
|
||||
} else if (item.startsWith('-t')) {
|
||||
_handler = IsolateManager.create(IsolateExe(BuildType.test));
|
||||
await isComplete();
|
||||
break;
|
||||
} else if (item.startsWith('-f')) {
|
||||
var file = '';
|
||||
if (parameters.length > i + 1) {
|
||||
file = parameters[i + 1];
|
||||
BuildType? buildType;
|
||||
String? filePath;
|
||||
bool isJson = false;
|
||||
bool hasUnknownOption = false;
|
||||
|
||||
if (parameters.isEmpty) {
|
||||
printHelp();
|
||||
return simpleFuture;
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
final param = parameters[i];
|
||||
if (param == '--json') {
|
||||
isJson = true;
|
||||
} else if (param == '-j') {
|
||||
buildType = BuildType.jenkins;
|
||||
} else if (param == '-t') {
|
||||
buildType = BuildType.test;
|
||||
} else if (param.startsWith('-f')) {
|
||||
buildType = BuildType.file;
|
||||
if (param == '-f') {
|
||||
if (i + 1 < parameters.length) {
|
||||
filePath = parameters[++i];
|
||||
} else {
|
||||
hasUnknownOption = true;
|
||||
}
|
||||
} else {
|
||||
file = item.replaceFirst('-f', '').trimLeft();
|
||||
filePath = param.substring(2);
|
||||
}
|
||||
} else if (param.startsWith('--file=')) {
|
||||
buildType = BuildType.file;
|
||||
filePath = param.substring('--file='.length);
|
||||
} else if (param == '--file') {
|
||||
buildType = BuildType.file;
|
||||
if (i + 1 < parameters.length) {
|
||||
filePath = parameters[++i];
|
||||
} else {
|
||||
hasUnknownOption = true;
|
||||
}
|
||||
await startYaml(file);
|
||||
break;
|
||||
} else {
|
||||
printHelp();
|
||||
hasUnknownOption = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUnknownOption || buildType == null) {
|
||||
printHelp();
|
||||
return simpleFuture;
|
||||
}
|
||||
|
||||
final logEnable = !isJson;
|
||||
|
||||
if (buildType == BuildType.file) {
|
||||
if (filePath == null || filePath.isEmpty) {
|
||||
printHelp();
|
||||
return simpleFuture;
|
||||
}
|
||||
await startYaml(filePath, logEnable: logEnable);
|
||||
} else {
|
||||
_handler = IsolateManager.create(IsolateExe(buildType, logEnable: logEnable));
|
||||
await isComplete();
|
||||
}
|
||||
|
||||
if (isJson && _resultJson != null) {
|
||||
await _printString(const JsonEncoder.withIndent(' ').convert(_resultJson));
|
||||
}
|
||||
|
||||
exitCode = _exitCode;
|
||||
_handler?.exit();
|
||||
return simpleFuture;
|
||||
|
|
@ -69,9 +129,13 @@ class CommandExe extends CommandBase {
|
|||
_ready = false;
|
||||
_complete = false;
|
||||
_exitCode = 0;
|
||||
_resultJson = null;
|
||||
_handler!.addEventListener(IsolateEvent.ready, (data) => _ready = true);
|
||||
_handler!.addListener('complete', (data) {
|
||||
if (data is int) {
|
||||
if (data is Map) {
|
||||
_resultJson = Map<String, dynamic>.from(data);
|
||||
_exitCode = (_resultJson!['exitCode'] as num?)?.toInt() ?? 10;
|
||||
} else if (data is int) {
|
||||
_exitCode = data;
|
||||
}
|
||||
_complete = true;
|
||||
|
|
@ -82,18 +146,28 @@ class CommandExe extends CommandBase {
|
|||
return simpleFuture;
|
||||
}
|
||||
|
||||
Future startYaml(String target) async {
|
||||
Future startYaml(String target, {bool logEnable = true}) async {
|
||||
var file = File(target);
|
||||
if (file.existsSync()) {
|
||||
_handler = IsolateManager.create(IsolateExe(BuildType.file,
|
||||
logEnable: logEnable,
|
||||
yamlContent: file.readAsStringSync(
|
||||
/*encoding: Platform.isWindows ? eucKr : systemEncoding*/)));
|
||||
await isComplete();
|
||||
} else {
|
||||
await CLI.print([
|
||||
CLI.style('There are no files in path "$target"', color: Color.red)
|
||||
]);
|
||||
_exitCode = 10;
|
||||
if (!logEnable) {
|
||||
final failureResult = BuildResult.failure(
|
||||
'There are no files in path "$target"',
|
||||
null,
|
||||
exitCode: 10,
|
||||
);
|
||||
_resultJson = failureResult.toJson();
|
||||
} else {
|
||||
await CLI.print([
|
||||
CLI.style('There are no files in path "$target"', color: Color.red)
|
||||
]);
|
||||
}
|
||||
}
|
||||
return simpleFuture;
|
||||
}
|
||||
|
|
@ -135,6 +209,7 @@ class IsolateExe extends IsolateBase {
|
|||
buildData: _build,
|
||||
logEnable: _logEnable,
|
||||
logPath: _logPath);
|
||||
send(IsoMessege('complete', data: result.exitCode));
|
||||
send(IsoMessege('complete', data: result.toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,9 +151,11 @@ class Application {
|
|||
//start build
|
||||
await pipeline.execute();
|
||||
} catch (e, stacktrace) {
|
||||
await CLI.printString(e.toString(), color: Color.redStrong);
|
||||
await CLI.printString(stacktrace.toString(), color: Color.yellowStrong);
|
||||
await printBuildStep('Build Failed', Color.redStrong);
|
||||
if (_logEnable) {
|
||||
await CLI.printString(e.toString(), color: Color.redStrong);
|
||||
await CLI.printString(stacktrace.toString(), color: Color.yellowStrong);
|
||||
await printBuildStep('Build Failed', Color.redStrong);
|
||||
}
|
||||
return BuildResult.failure(e, stacktrace);
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +181,9 @@ class Application {
|
|||
|
||||
static void log(String message, {LogType logType = LogType.verbose}) {
|
||||
if (_logFile == null) {
|
||||
print(message);
|
||||
if (_logEnable) {
|
||||
print(message);
|
||||
}
|
||||
} else {
|
||||
if (_logEnable || logType == LogType.error) {
|
||||
_logFile?.writeAsStringSync('[${getDates()}] $message\n',
|
||||
|
|
@ -287,6 +291,7 @@ class Application {
|
|||
}
|
||||
|
||||
Future printBuildStep(String name, Color? color) async {
|
||||
if (!_logEnable) return simpleFuture;
|
||||
var message =
|
||||
'''$enter*********************************************************************************************
|
||||
* $name
|
||||
|
|
|
|||
|
|
@ -12,4 +12,23 @@ class BuildResult {
|
|||
|
||||
const BuildResult.failure(this.error, this.stackTrace, {this.exitCode = 10})
|
||||
: success = false;
|
||||
|
||||
String get message => success
|
||||
? 'Build completed successfully.'
|
||||
: (error?.toString() ?? 'Build failed.');
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'schemaVersion': 1,
|
||||
'type': 'executionResult',
|
||||
'success': success,
|
||||
'exitCode': exitCode,
|
||||
'message': message,
|
||||
'error': success
|
||||
? null
|
||||
: {
|
||||
'type': error.runtimeType.toString(),
|
||||
'message': message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:oto/cli/commands/command_exe.dart';
|
||||
import 'package:oto/oto/application.dart';
|
||||
import 'package:oto/oto/core/build_result.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -450,4 +453,181 @@ scheduler: "not_a_map"
|
|||
expect(result.message, isNotEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('BuildResult.toJson', () {
|
||||
test('exposes success execution envelope', () {
|
||||
const result = BuildResult.success();
|
||||
final json = result.toJson();
|
||||
|
||||
expect(json['schemaVersion'], 1);
|
||||
expect(json['type'], 'executionResult');
|
||||
expect(json['success'], isTrue);
|
||||
expect(json['exitCode'], 0);
|
||||
expect(json['message'], 'Build completed successfully.');
|
||||
expect(json['error'], isNull);
|
||||
});
|
||||
|
||||
test('exposes failure execution envelope', () {
|
||||
final error = Exception('Some critical error');
|
||||
final stack = StackTrace.current;
|
||||
final result = BuildResult.failure(error, stack, exitCode: 15);
|
||||
final json = result.toJson();
|
||||
|
||||
expect(json['schemaVersion'], 1);
|
||||
expect(json['type'], 'executionResult');
|
||||
expect(json['success'], isFalse);
|
||||
expect(json['exitCode'], 15);
|
||||
expect(json['message'], contains('Some critical error'));
|
||||
expect(json['error'], isNotNull);
|
||||
expect(json['error']['type'], '_Exception');
|
||||
expect(json['error']['message'], contains('Some critical error'));
|
||||
});
|
||||
});
|
||||
|
||||
group('logEnable false', () {
|
||||
test('build with logEnable false suppresses failure stdout', () async {
|
||||
const invalidYaml = '''
|
||||
property:
|
||||
workspace: .
|
||||
commands:
|
||||
- command: Print
|
||||
id: hello
|
||||
param:
|
||||
message: hi
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: doesNotExist
|
||||
''';
|
||||
|
||||
final prints = <String>[];
|
||||
final result = await runZoned(() async {
|
||||
return await Application.instance.build(
|
||||
BuildType.file,
|
||||
yamlContent: invalidYaml,
|
||||
logEnable: false,
|
||||
);
|
||||
}, zoneSpecification: ZoneSpecification(
|
||||
print: (self, parent, zone, line) {
|
||||
prints.add(line);
|
||||
},
|
||||
));
|
||||
|
||||
expect(result.success, isFalse);
|
||||
expect(prints, isEmpty);
|
||||
});
|
||||
|
||||
test('build with logEnable false suppresses command stdout logs', () async {
|
||||
const yaml = '''
|
||||
property:
|
||||
workspace: .
|
||||
commands:
|
||||
- command: Print
|
||||
id: hello
|
||||
param:
|
||||
message: hi
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: hello
|
||||
''';
|
||||
|
||||
final prints = <String>[];
|
||||
final result = await runZoned(() async {
|
||||
return await Application.instance.build(
|
||||
BuildType.file,
|
||||
yamlContent: yaml,
|
||||
logEnable: false,
|
||||
);
|
||||
}, zoneSpecification: ZoneSpecification(
|
||||
print: (self, parent, zone, line) {
|
||||
prints.add(line);
|
||||
},
|
||||
));
|
||||
|
||||
expect(result.success, isTrue);
|
||||
expect(prints, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('CommandExe JSON Mode', () {
|
||||
test('CommandExe shouldPrintExecuteLog suppresses execute log for json', () {
|
||||
final command = CommandExe();
|
||||
expect(command.shouldPrintExecuteLog(['--json']), isFalse);
|
||||
expect(command.shouldPrintExecuteLog(['-f', 'file.yaml']), isTrue);
|
||||
});
|
||||
|
||||
test('CommandExe exe -f missing file --json returns execution result json', () async {
|
||||
final missing = '/tmp/oto_missing_${DateTime.now().microsecondsSinceEpoch}.yaml';
|
||||
expect(File(missing).existsSync(), isFalse);
|
||||
|
||||
final prints = <String>[];
|
||||
final command = CommandExe(printString: (value) async {
|
||||
prints.add(value);
|
||||
});
|
||||
|
||||
final priorExitCode = exitCode;
|
||||
try {
|
||||
await command.execute(['-f', missing, '--json']);
|
||||
} finally {
|
||||
exitCode = priorExitCode;
|
||||
}
|
||||
|
||||
expect(prints, hasLength(1));
|
||||
final json = jsonDecode(prints[0]);
|
||||
expect(json['schemaVersion'], 1);
|
||||
expect(json['type'], 'executionResult');
|
||||
expect(json['success'], isFalse);
|
||||
expect(json['exitCode'], 10);
|
||||
expect(json['message'], contains('There are no files in path'));
|
||||
});
|
||||
|
||||
test('actual bin exe --json output has parseable execution result json and no execute logs', () async {
|
||||
final tempFile = File('${Directory.systemTemp.path}/oto_temp_${DateTime.now().microsecondsSinceEpoch}.yaml');
|
||||
const yamlContent = '''
|
||||
property:
|
||||
workspace: .
|
||||
commands:
|
||||
- command: Print
|
||||
id: hello
|
||||
param:
|
||||
message: hi
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: hello
|
||||
''';
|
||||
tempFile.writeAsStringSync(yamlContent);
|
||||
|
||||
try {
|
||||
final result = await Process.run('dart', [
|
||||
'run',
|
||||
'bin/main.dart',
|
||||
'exe',
|
||||
'-f',
|
||||
tempFile.path,
|
||||
'--json',
|
||||
]);
|
||||
|
||||
expect(result.exitCode, 0);
|
||||
|
||||
final stdoutStr = result.stdout.toString().trim();
|
||||
|
||||
expect(stdoutStr, isNotEmpty);
|
||||
|
||||
final json = jsonDecode(stdoutStr);
|
||||
expect(json['schemaVersion'], 1);
|
||||
expect(json['type'], 'executionResult');
|
||||
expect(json['success'], isTrue);
|
||||
expect(json['exitCode'], 0);
|
||||
|
||||
expect(stdoutStr, isNot(contains('Execute command:')));
|
||||
expect(stdoutStr, isNot(contains('hi')));
|
||||
} finally {
|
||||
if (tempFile.existsSync()) {
|
||||
tempFile.deleteSync();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue