Skip to content

GRB-02 백엔드 검증 루프 hardening과 Docker preflight 추가#75

Merged
alexization merged 2 commits intodevelopfrom
feat/grb-02-backend-verification-hardening
Mar 26, 2026
Merged

GRB-02 백엔드 검증 루프 hardening과 Docker preflight 추가#75
alexization merged 2 commits intodevelopfrom
feat/grb-02-backend-verification-hardening

Conversation

@alexization
Copy link
Copy Markdown
Owner

@alexization alexization commented Mar 26, 2026

1) 요약

  • integrationTest 전에 verifyDockerAvailable preflight를 추가해 Docker 환경 문제를 Testcontainers 예외보다 먼저 fail-fast 하도록 정리했습니다.
  • Docker CLI/daemon 상태를 설명하는 테스트 지원 코드와 unit test를 추가했고, README에 backend 검증 순서를 문서화했습니다.

2) 연관 이슈

3) 문제와 목표

  • 문제: Docker daemon이 내려가 있으면 ./gradlew integrationTestContainerFetchExceptionDockerClientProviderStrategy 예외로 바로 실패해 환경 문제와 코드 회귀를 빠르게 구분하기 어려웠습니다.
  • 사용자/운영자 관점의 결과: Docker가 없을 때는 원인과 다음 조치가 보이는 메시지로 즉시 실패하고, Docker가 준비된 환경에서는 기존 통합 테스트 경로를 그대로 유지합니다.
  • 비목표: 비즈니스 기능 변경, Testcontainers 테스트 구조 전면 리팩터링, CI 파이프라인 개편

4) 영향 범위

  • 변경된 패키지/모듈: build.gradle, src/test/java/com/gitranker/api/testsupport/*, README.md
  • API/DTO/Schema 영향: 없음
  • DB/Cache/Batch/Scheduler 영향: 통합 테스트 진입 전 Docker preflight 추가 외 없음
  • 보안/권한 영향: 없음

5) 검증 증거

유형 명령어 / 증거 결과
Build ./gradlew verifyDockerAvailable 성공. Docker preflight passed. context=orbstack serverApiVersion=1.51 확인
Unit ./gradlew test --tests "com.gitranker.api.testsupport.DockerPreflightCheckTest" 성공
Integration ./gradlew integrationTest 성공
Coverage ./gradlew test jacocoTestCoverageVerification 성공
API/Manual Smoke 미실행(검증 루프/문서 작업이라 API 수동 smoke는 범위 아님) -

추가 확인:

  • Docker 미기동 상태의 ./gradlew verifyDockerAvailable./gradlew integrationTest에서 preflight fail-fast 메시지 출력 확인

6) 관측성 확인

  • 확인한 로그: 미확인(관측성 변경 없음)
  • 확인한 메트릭: 미확인(메트릭 변경 없음)
  • 확인한 trace/dashboard/query: 미확인(해당 범위 아님)

7) AI 리뷰 메모 (선택)

  • Codex: Docker preflight helper와 Gradle JavaExec task로 환경 문제를 테스트 가능하게 분리함
  • CodeRabbitAI: 미실행

8) 리스크 및 롤백

  • 리스크: docker version --format "{{.Server.APIVersion}}"를 지원하지 않는 매우 오래된 Docker CLI 환경에서는 추가 보정이 필요할 수 있습니다.
  • 롤백 계획: verifyDockerAvailable task와 integrationTest dependency를 되돌리고 README verification 섹션을 제거합니다.

9) 체크리스트

  • 연관 이슈가 연결되어 있음
  • Build / Unit / Integration 결과가 기입되어 있음
  • API/스키마/배치 영향이 반영되었거나 없음을 명시함
  • 로그/메트릭/trace 확인 내용을 적었거나 불필요 사유를 적음
  • 문서 또는 후속 이슈가 업데이트되었거나 불필요 사유를 적음

Summary by CodeRabbit

릴리스 노트

  • Documentation

    • 단위 테스트, 커버리지 검증, 통합 테스트 실행을 포함한 로컬 개발 검증 프로세스 문서화
  • Tests

    • Docker 환경 사용 가능 여부를 확인하는 프리플라이트 검사 도구 추가
  • Chores

    • 통합 테스트 실행 전 Docker 가용성을 검증하는 빌드 작업 추가

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: bfd25790-7922-4435-8a82-16d8b91e210a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Docker 가용성을 확인하는 preflight 검증 시스템을 도입했습니다. 새로운 verifyDockerAvailable Gradle 태스크가 통합 테스트 전에 Docker를 확인하고, 명확한 진단 메시지를 제공하는 DockerPreflightCheck 클래스로 빌드된 fail-fast 메커니즘을 추가했습니다.

Changes

Cohort / File(s) Summary
Documentation
README.md
"🧪 Development Verification" 섹션 추가: 단위 테스트, 커버리지 검증, Docker 가용성 확인, 통합 테스트의 로컬 검증 워크플로우 문서화.
Gradle Build Configuration
build.gradle
verifyDockerAvailable JavaExec 태스크 추가 (DockerPreflightMain 실행); integrationTest 태스크에 해당 의존성 추가하여 Docker preflight를 통합 테스트 전에 실행.
Docker Preflight Implementation
src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java, DockerPreflightMain.java
docker context showdocker version 명령 실행, 성공/실패 판별, 진단 메시지 생성 로직 구현. ProcessCommandRunner를 통한 명령 실행, CommandResult/DockerPreflightResult 레코드로 결과 캡슐화.
Test Coverage
src/test/java/com/gitranker/api/testsupport/DockerPreflightCheckTest.java
Docker daemon 도달 가능, 도달 불가, CLI 누락 등 세 가지 시나리오를 검증하는 JUnit 5 테스트 클래스.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Docker의 문을 두드려보니

선한 전령이 나타났네!

Preflight 확인으로 빠른 실패,

혼란스러운 예외는 이제 안녕!

개발 검증 루프는 이제 명확하리~

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly describes the main change: adding Docker preflight verification and backend verification loop hardening, which matches the PR objectives and code changes.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #74: Docker preflight with fail-fast messaging, Gradle integration flow modification, test support code with unit tests, and README documentation.
Out of Scope Changes check ✅ Passed All changes are strictly within the scope of issue #74: Docker preflight implementation, Gradle task integration, supporting test code, and documentation updates. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/grb-02-backend-verification-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@alexization alexization self-assigned this Mar 26, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java (2)

95-97: CommandResult.failure() 팩토리 메서드에서 exitCode 검증 누락

failure() 팩토리 메서드가 exitCode == 0을 허용합니다. 의미론적으로 failure는 non-zero exit code를 가져야 하므로, 잘못된 사용을 방지하기 위해 검증을 추가하는 것이 좋습니다.

♻️ exitCode 검증 추가
 static CommandResult failure(int exitCode, String stdout, String stderr) {
+    if (exitCode == 0) {
+        throw new IllegalArgumentException("Failure result must have non-zero exit code");
+    }
     return new CommandResult(exitCode, stdout, stderr);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java` around
lines 95 - 97, The failure() factory currently allows exitCode == 0; update
CommandResult.failure(int exitCode, String stdout, String stderr) to validate
that exitCode is non-zero and throw an IllegalArgumentException (or similar)
when exitCode == 0 to enforce the semantic that failure must have a non-zero
exit code; locate the failure method in DockerPreflightCheck/Test support and
add the check at the start of the method (e.g., if (exitCode == 0) throw new
IllegalArgumentException("failure exitCode must be non-zero")).

135-140: 스트림 읽기 순서로 인한 잠재적 데드락

process.waitFor() 호출 전에 stdoutstderr를 순차적으로 readAllBytes()로 읽고 있습니다. 프로세스 출력이 OS 파이프 버퍼(보통 64KB)를 초과하면 프로세스가 블록되고, 동시에 Java 코드도 다른 스트림을 기다리며 데드락이 발생할 수 있습니다.

docker version 출력은 일반적으로 매우 작아서 실제 문제가 될 가능성은 낮지만, 더 안전한 패턴은 스트림을 별도 스레드에서 drain하거나 ProcessBuilder.redirectErrorStream(true)를 사용하는 것입니다.

♻️ 더 안전한 스트림 처리 예시 (선택 사항)
 `@Override`
 public CommandResult run(List<String> command) {
     ProcessBuilder processBuilder = new ProcessBuilder(command);
+    processBuilder.redirectErrorStream(false);

     try {
         Process process = processBuilder.start();
-        String stdout = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
-        String stderr = new String(process.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
-        int exitCode = process.waitFor();
+        CompletableFuture<String> stdoutFuture = CompletableFuture.supplyAsync(() -> readStream(process.getInputStream()));
+        CompletableFuture<String> stderrFuture = CompletableFuture.supplyAsync(() -> readStream(process.getErrorStream()));
+        int exitCode = process.waitFor();
+        String stdout = stdoutFuture.join();
+        String stderr = stderrFuture.join();
         return new CommandResult(exitCode, stdout, stderr);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java` around
lines 135 - 140, The current implementation in DockerPreflightCheck reads
process.getInputStream().readAllBytes() and then
process.getErrorStream().readAllBytes() before calling process.waitFor(), which
can deadlock for large outputs; fix by draining both streams concurrently or by
configuring the ProcessBuilder to merge streams. Update the logic around
processBuilder.start()/Process usage in the method that returns CommandResult so
that stdout and stderr are read asynchronously (e.g., spawn two reader
threads/futures to collect streams while the process runs) or set
processBuilder.redirectErrorStream(true) and only read the merged stream, then
call process.waitFor() and construct the CommandResult from the collected
output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java`:
- Around line 95-97: The failure() factory currently allows exitCode == 0;
update CommandResult.failure(int exitCode, String stdout, String stderr) to
validate that exitCode is non-zero and throw an IllegalArgumentException (or
similar) when exitCode == 0 to enforce the semantic that failure must have a
non-zero exit code; locate the failure method in DockerPreflightCheck/Test
support and add the check at the start of the method (e.g., if (exitCode == 0)
throw new IllegalArgumentException("failure exitCode must be non-zero")).
- Around line 135-140: The current implementation in DockerPreflightCheck reads
process.getInputStream().readAllBytes() and then
process.getErrorStream().readAllBytes() before calling process.waitFor(), which
can deadlock for large outputs; fix by draining both streams concurrently or by
configuring the ProcessBuilder to merge streams. Update the logic around
processBuilder.start()/Process usage in the method that returns CommandResult so
that stdout and stderr are read asynchronously (e.g., spawn two reader
threads/futures to collect streams while the process runs) or set
processBuilder.redirectErrorStream(true) and only read the merged stream, then
call process.waitFor() and construct the CommandResult from the collected
output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 07eaa1f8-d1bc-4980-a9b5-0b1f623ebf01

📥 Commits

Reviewing files that changed from the base of the PR and between 8b434a6 and 67234f6.

📒 Files selected for processing (5)
  • README.md
  • build.gradle
  • src/test/java/com/gitranker/api/testsupport/DockerPreflightCheck.java
  • src/test/java/com/gitranker/api/testsupport/DockerPreflightCheckTest.java
  • src/test/java/com/gitranker/api/testsupport/DockerPreflightMain.java

@alexization
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67234f6124

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +137 to +138
String stdout = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
String stderr = new String(process.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Consume process streams concurrently to avoid deadlock

Reading stdout to EOF and only then reading stderr can deadlock this preflight task when docker writes enough data to stderr before exiting: the child process blocks on a full error pipe, while the parent is still blocked in readAllBytes() on stdout. In that case verifyDockerAvailable (and therefore integrationTest) can hang indefinitely instead of failing fast, especially under verbose Docker/plugin error output.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

CodeRabbit nitpick 2건 반영했습니다.

  • CommandResult.failure()에서 exitCode == 0 방지 검증 추가
  • ProcessCommandRunner가 stdout/stderr를 비동기로 drain하도록 변경해 잠재적 데드락 리스크 제거

재검증:

  • ./gradlew test --tests "com.gitranker.api.testsupport.DockerPreflightCheckTest"
  • ./gradlew verifyDockerAvailable
  • ./gradlew test jacocoTestCoverageVerification
  • ./gradlew integrationTest

후속 커밋: e31a996

@alexization alexization merged commit 64c0541 into develop Mar 26, 2026
3 checks passed
@alexization alexization deleted the feat/grb-02-backend-verification-hardening branch March 26, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant