- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
148 lines
14 KiB
Markdown
148 lines
14 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a20-f003-claude
|
|
title: grpc 레인 시험 25개는 check 로 돌고, 레인만 거는 실행 0 검사는 돌지 않는다
|
|
topic: grpc-and-streaming
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:a20-f003-claude
|
|
evidenceCapturedOn: 2026-09-04
|
|
body: case-a20-f003-claude.body.md
|
|
assets:
|
|
- key: a20-f003-claude
|
|
file: ../../../final/evidence/rendered/a20-f003-claude.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a20-f003-claude.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#a20#L260 이다.
|
|
---
|
|
|
|
# grpc 레인 시험 25개는 check 로 돌고, 레인만 거는 실행 0 검사는 돌지 않는다
|
|
|
|
원문은 증거 레인의 시험 스물다섯 개에 자동 실행 경로가 없다고 적었다. `:grpc:grpc-testkit` 의 두 태스크를 돌려 시험 이름을 집합으로 비교하니 기본 `test` 의 56 개가 그 스물다섯을 전부 포함한다. 자동 경로에 없는 것은 시험이 아니라 레인이 거는 실행 0 검사다.
|
|
|
|
## 관계
|
|
|
|
- **아무도 돌리지 않는 레인의 게이트는 마지막으로 돌린 사람이 본 것을 보고한다**
|
|
원문이 이 사례를 그 규칙으로 읽었다. 시험 쪽에는 붙지 않고, 레인만 거는 실행 0 검사 쪽에 붙는다.
|
|
- **네 레인이 check 에 붙지 않고, 이 가족을 이름으로 부르는 워크플로가 없다**
|
|
그 기록이 25개 시험을 직접 입력할 때만 도는 것으로 적었는데, 기본 `test` 가 그 25개를 포함한다.
|
|
- **release gate가 실제로 차단하는 것은 hermetic test 3개이고, mongo용 CI workflow는 없다**
|
|
mongo 가족에서도 레인을 이름으로 부르는 워크플로가 없어서, 게이트가 실제로 차단하는 것은 기본 `test` 에 들어 있는 밀폐 클래스뿐이다.
|
|
|
|
## 문제
|
|
|
|
src/grpc/CLAUDE.md 가 세 레인의 현재 상태를 통과로 서술한다.
|
|
|
|
그 문장의 주어가 무엇이고 어느 경로로 실행되는지 확인했다.
|
|
|
|
## 결론
|
|
|
|
앞 절반은 사실이다. grpcInProcessContractTest·grpcNettyContractTest·grpcFaultTest 를 이름으로 실행하니 모두 BUILD SUCCESSFUL 이고 시험 수는 7·9·9 다.
|
|
|
|
이어서 grpc-testkit 의 기본 test 를 실행했다. 7 클래스 56 개가 통과하는데, 시험 이름을 하나씩 맞춰 보면 레인 쪽 25 개가 빠짐없이 그 안에 들어 있다.
|
|
|
|
겹치는 이유는 제외 태그 목록에 있다. 레인은 태그로 시험을 고르고 그 시험들은 공유 test 소스 세트에 있는데, 기본 test 가 제외하는 것은 루트 규약의 quarantine 과 grpc-testkit/build.gradle:42 의 grpc-performance 뿐이다. ci-quality-gates.yml:50 이 ./gradlew check 를 돌리므로 이 25 개에는 사람이 부르지 않아도 도는 경로가 있다.
|
|
|
|
넷째 레인만 다르다. GrpcPerformanceLaneTest 는 @Tag("grpc-performance") 를 달아 기본 test 결과에 0 건이다.
|
|
|
|
자동 경로에 없는 것은 따로 있다. ca.strict-test-lane.gradle:293 의 최신 재사용 거부와, :246·:257 이 실행한 시험 수를 세어 0 이면 GradleException 을 던지는 검사다. failOnNoDiscoveredTests 는 이 둘에 들어가지 않는다. 레인을 쓰지 않는 leaf 의 테스트 태스크에서도 같은 값이라 빌드 도구 기본값이다.
|
|
|
|
@Tag 가 지워지면 기본 test 는 같은 수를 계속 돌리고, 클래스가 지워지면 더 적은 수를 돌리며 그대로 통과한다. :grpc:grpc-testkit:check 의 dry-run 그래프에 레인 태스크는 0 건이고, grpc-testkit/build.gradle 에 check 가 0 번 나온다.
|
|
|
|
.github/workflows 의 파일 어디에도 grpc 라는 문자열이 없다. 다만 ci-quality-gates.yml:61 의 conditionalTransportQualification 이 src/build.gradle:606 을 통해 :adapter:inbound:grpc:grpcTransportQualificationTest 를 요구하므로, 자동으로 도는 grpc 레인이 다른 프로젝트에 하나 있다.
|
|
|
|
원본 분석의 등급은 P2 이고 이 기록은 새로 매기지 않는다. 그 등급을 떠받치던 근거 — 스물다섯 개 시험이 자동 실행 밖에 있다 — 는 성립하지 않는다. 남는 것은 레인만 거는 두 보증이 자동 경로에 없다는 더 좁은 결함이다.
|
|
|
|
## 검증 환경
|
|
|
|
Gradle : 9.0.0
|
|
확인 방식 : 레인 등록 블록과 태그 확인, 기본 test 에 걸리는 제외 태그를 루트 규약과 leaf 양쪽에서 확인, 태그가 클래스 단위인지 확인, :grpc:grpc-testkit:check dry-run 으로 태스크 그래프 확인, 결과 디렉터리를 비우고 실행 전 XML 0 건 확인 후 세 레인과 기본 test 를 각각 실행하고 종료 코드로 집계를 막음, 두 실행의 시험 이름 집합 비교, 성능 레인 클래스의 포함 여부 확인, 레인 규약의 보증 구현 확인, failOnNoDiscoveredTests 를 레인 없는 leaf 와 대조, check 언급과 워크플로 검색, 지속 통합이 부르는 다른 grpc 레인 확인
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
1. 레인을 등록하는 블록에서 이름과 태그를 모은다.
|
|
2. 기본 테스트 태스크에 걸리는 제외 태그를 루트 규약과 leaf 빌드 파일 양쪽에서 찾는다.
|
|
3. 레인 태그가 클래스 선언에 붙는지 확인한다.
|
|
4. 검사 태스크를 dry-run 해 그래프에 기본 테스트가 있고 레인 태스크가 없는 것을 본다.
|
|
5. 결과 디렉터리를 비우고 남은 XML 이 0 인지 찍는다.
|
|
6. 세 레인을 이름으로 실행하고 종료 코드를 확인한 뒤에만 결과를 집계한다.
|
|
7. 같은 leaf 의 기본 테스트 태스크를 같은 방식으로 실행하고 집계한다.
|
|
8. 두 실행이 남긴 시험 이름을 모아 한쪽이 다른 쪽에 포함되는지 판정한다.
|
|
9. 제외 태그를 단 클래스가 기본 실행 결과에 있는지 센다.
|
|
10. 레인 규약에서 기본 태스크에 없는 보증을 찾고, failOnNoDiscoveredTests 를 레인이 없는 leaf 와 대조한다.
|
|
11. 이 leaf 의 빌드 파일이 검사 태스크를 언급하는지, 워크플로가 이 가족을 이름으로 부르는지 세고, 다른 프로젝트의 grpc 레인이 자동으로 도는지 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
`src/grpc/CLAUDE.md` 는 in-process·Netty·fault 레인이 실제로 실행되어 통과한다고 적는다. 원본 분석은 앞 절반을 사실로 확인한 뒤, 그 레인들이 `check` 에 없고 어떤 워크플로도 이름으로 부르지 않으므로 스물다섯 개 시험이 누군가 명령을 입력할 때만 돈다고 적었다.
|
|
|
|
## 레인 넷과 기본 test 가 제외하는 태그
|
|
|
|
:::evidence key="a20-f003-claude" alt="src 디렉터리에서 돌린 gradle 실행과 정적 검색을 합친 출력 134줄. 먼저 gradle 판이 나오고, grpc-testkit 의 build.gradle 이 등록하는 레인 넷과 태그, 그리고 기본 test 에 걸리는 제외 태그가 루트 규약의 quarantine 과 leaf 의 grpc-performance 두 곳에서 온다는 것이 원문과 함께 실린다. 세 레인 태그가 클래스 선언 바로 위에 붙어 있는 것이 보인다. 이어서 check 를 dry-run 한 태스크 그래프가 나오는데 test 와 check 는 있고 레인 태스크는 0 건이다. 그 다음 결과 디렉터리를 지우고 실행 전 남은 XML 이 0 건임을 찍은 뒤 세 레인을 이름으로 돌려 GRADLE_EXIT=0 과 클래스별 7·9·9 가 실패 0 으로 나오고, 이어서 기본 test 를 돌려 GRADLE_EXIT=0 과 7 클래스 56 개가 나온다. 두 실행의 시험 이름을 집합으로 비교해 레인 25 개가 기본 test 56 개 안에 전부 있다는 판정이 True 로 찍히고 기본 test 에만 있는 시험이 31 개라는 것, 성능 레인 클래스가 기본 test 결과에 0 건이라는 것이 나온다. 마지막으로 레인 규약에서 보증에 해당하는 줄들이 인용되고, grpc-testkit 의 build.gradle 이 check 를 0 번 언급하며 워크플로 28 개 중 grpc 문자열을 담은 것이 0 건인데 ci-quality-gates 의 48번부터 61번 줄에는 check 가 모든 레인을 덮지 않는다는 주석과 별도 스텝 둘이 있고, 그중 마지막 스텝이 부르는 태스크가 grpc 이름의 레인을 요구한다는 것이 나온다." caption="레인 넷과 태그 · 기본 test 의 제외 태그 두 곳 · check 그래프에 레인 0 건 · 실행 전 XML 0 건과 GRADLE_EXIT · 레인 25 개가 기본 test 56 개의 부분집합 True · 레인 규약의 보증 · check 가 레인을 덮지 않는다는 주석과 grpc 레인을 부르는 별도 스텝 — 134줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
`grpc-testkit/build.gradle:14`\~`:35` 가 레인 넷을 등록한다. `grpcInProcessContractTest`, `grpcNettyContractTest`, `grpcFaultTest`, `grpcPerformanceTest` 이고 태그는 `grpc-inprocess`, `grpc-netty`, `grpc-fault`, `grpc-performance` 다. 세 태그는 클래스 선언 바로 위에 붙어 클래스 전체를 덮는다.
|
|
|
|
기본 `test` 에 걸리는 제외 태그는 두 곳에서 온다. 루트 규약 `src/build.gradle:551` 의 `quarantine` 과 `grpc-testkit/build.gradle:42` 의 `grpc-performance` 다. `grpc-testkit` 의 시험에 `quarantine` 태그는 0 건이므로 이 leaf 에서 실효 제외는 성능 태그 하나다. 나머지 세 태그는 어느 쪽에도 없다.
|
|
|
|
## 레인 25 개는 기본 test 56 개 안에 있다
|
|
|
|
`:grpc:grpc-testkit:check` 를 dry-run 했다. 그래프에 `:grpc:grpc-testkit:test` 가 있고 레인 태스크는 0 건이다. 시험을 돌리지 않고 얻은 결과다.
|
|
|
|
그다음 결과 디렉터리를 지우고 돌렸다. 실행 전 남은 XML 이 0 건임을 찍은 뒤, 세 레인을 이름으로 돌려 `GRADLE_EXIT=0` 과 `GrpcInProcessContractFixtureTest` 7, `GrpcNettyContractProfileTest` 9, `GrpcTransportEvidenceClassifierTest` 9 를 얻었다. 이어서 기본 `test` 를 돌려 `GRADLE_EXIT=0` 과 7 클래스 56 개를 얻었다.
|
|
|
|
두 실행의 시험 이름을 집합으로 비교했다. 레인이 돌린 25 개가 기본 `test` 의 56 개 안에 전부 있다. 기본 `test` 에만 있는 시험은 31 개다.
|
|
|
|
`GrpcPerformanceLaneTest` 는 기본 `test` 결과에 0 건이다. 제외 태그가 그 클래스를 걸러낸다.
|
|
|
|
`ci-quality-gates.yml:50` 이 `./gradlew check` 를 돌린다. 그러므로 이 25 개에는 사람이 이름을 입력하지 않아도 도는 경로가 있다.
|
|
|
|
## 레인만 거는 보증 둘은 check 에 없다
|
|
|
|
레인 태스크가 기본 `test` 와 다른 점은 둘이다.
|
|
|
|
하나는 `ca.strict-test-lane.gradle:293` 의 `outputs.upToDateWhen { false }` 다. 레인은 결과를 최신으로 재사용하지 않는다.
|
|
|
|
다른 하나는 실행한 시험 수를 세어 0 을 거부하는 것이다. `:246` 이 `executedTests` 를 두고 `:250` 이 시험마다 증가시키며, `:257` 이 그 값이 0 이면 `GradleException` 을 던진다. `:239`\~`:245` 는 왜 그것이 필요한지 적는다 — `failOnNoDiscoveredTests` 는 발견 단계에 적용되는데 태그 필터는 발견 이후에 시험을 걸러 내므로, 태그가 아무것도 맞히지 못한 레인은 그것만으로는 성공으로 끝난다는 것이다.
|
|
|
|
`failOnNoDiscoveredTests` 자체는 레인만의 것이 아니다. 레인이 없는 `:grpc:grpc-core-api:test` 에서도 `true` 이므로 Gradle 9.0.0 의 기본값이다. `:23` 이 적는 레인의 몫은 그 값을 갖는 것이 아니라 leaf 가 그것을 끄지 못하게 하는 것이다.
|
|
|
|
`@Tag("grpc-netty")` 가 지워지면 그 클래스는 여전히 test 소스 세트에 있으므로 기본 `test` 는 같은 수를 돌린다. 클래스 자체가 지워지면 기본 `test` 는 더 적은 수를 돌리고 그대로 통과한다. 두 경우 모두 레인은 실행 0 으로 실패하는데, 레인을 부르는 자동 경로가 없다.
|
|
|
|
`grpc-testkit/build.gradle` 은 `check` 를 한 번도 언급하지 않는다. 워크플로 스물여덟 개 중 `grpc` 문자열을 담은 것은 0 건이다.
|
|
|
|
다만 그 0 건이 이 가족 전체를 뜻하지는 않는다. `ci-quality-gates.yml:61` 이 `conditionalTransportQualification` 을 돌리고, `src/build.gradle:606` 이 그 태스크에 `:adapter:inbound:grpc:grpcTransportQualificationTest` 를 건다. 지속 통합이 자동으로 부르는 grpc 이름의 레인이 하나 있고, 그것은 `:grpc:grpc-testkit` 의 레인 넷이 아니다.
|
|
|
|
## 원문과 갈리는 자리
|
|
|
|
원문은 `:286` 에서 `ci-quality-gates.yml` 이 `check` 를 돌리므로 각 leaf 의 기본 `test` 는 지속 통합에서 실행된다고 스스로 적었다. 그리고 바로 다음 문단에서 그 25 개가 누군가 명령을 직접 입력할 때만 돈다고 적었다. 두 문장이 함께 설 수 없다.
|
|
|
|
성립하는 쪽은 앞 문장이다. 세 레인의 시험은 태그가 붙은 채 공유 test 소스 세트에 있고, 기본 `test` 가 제외하는 것은 성능 태그와 `quarantine` 뿐이다. 집합 비교가 25 개 전부의 포함을 보인다.
|
|
|
|
원문이 인용한 문장 — 아무도 지역에서 돌리지 않는 레인의 붉은 게이트는 마지막으로 돌린 사람이 본 것을 보고한다 — 은 시험 자체에는 붙지 않는다. 붙는 것은 레인이 거는 실행 0 검사 쪽이다.
|
|
|
|
원문은 레인을 `:286` 에서 "네 증거 레인"으로, `:8` 과 `:465` 에서 "증거 레인 3종"으로 부른다. 등록된 것은 넷이고 돌린 것은 셋이며 넷째는 기본 `test` 에서 의도적으로 제외된 성능 레인이므로 두 표기 모두 대상이 다를 뿐 틀리지 않았다.
|
|
|
|
## 등급에 대해
|
|
|
|
원본 분석의 등급은 P2 다. 제목이 든 근거 — 증거 등급 모델 전체가 자동 실행 경로 밖에 있다 — 는 이 리비전에서 성립하지 않는다.
|
|
|
|
남는 것은 더 좁은 결함이다. 레인이 존재하는 이유인 실행 0 검사와 최신 재사용 거부가 자동 경로에 없다. 이 기록은 등급을 새로 매기지 않고 상류가 P2 로 둔 근거와 여기서 확인한 반대 근거를 함께 남긴다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
태그 삭제와 클래스 삭제를 실제로 해 보고 두 태스크가 어떻게 갈리는지 관측하지는 않았다. 필터 구성과 규약 코드를 읽었고, 겹치는 시험은 이름 대조로 확인했다.
|
|
|
|
GitHub 러너 위에서 검사 태스크를 돌리지 않았다. 워크플로에 적힌 명령과 로컬 dry-run 그래프를 이어 붙여 판단했다.
|
|
|
|
지속 부하와 성능 기준선은 확인 범위 밖이다. 지침 문서도 그것이 없다고 적는다.
|
|
|
|
`@Tag` 를 지우거나 클래스를 지운 상태를 만들어 두 태스크의 결과가 갈리는 것을 실행으로 보이지 않았다. 두 태스크의 필터 구성과 레인 규약의 실행 0 검사를 읽고, 같은 시험이 양쪽에서 도는 것을 집합 비교로 확인한 데까지다.
|
|
|
|
<!-- body:end -->
|