[base] Add nocompile tests for SequenceBound
MERGED2026base
2026. 8. 24.
Indigochi1d
base::SequenceBound의 AsyncCall() 빌더 체인이 강제하는 호출 순서 계약을 검증하는 nocompile 테스트를 추가하고,
그 자리를 차지하고 있던 빈 껍데기 테스트를 제거했습니다. 2020년부터 6년간 방치된 TODO를 구현한 작업입니다.
문제 설명
SequenceBound의 메서드 호출은 AsyncCall → WithArgs → Then 순서를 지켜야 한다. AsyncCallBuilderImpl의 부분 특수화 3종이 각 단계에서 유효한 메서드만 노출해서 이를 강제한다.
sequence_bound_unittest.cc의TYPED_TEST(SequenceBoundTest, NoCompileTests)는 본문 33줄이 전부 주석이었다. 검증하는 게 없었다.- TYPED_TEST라 두 변형으로 등록되어 항상 통과했다. 테스트 목록에는 검증이 있는 것처럼 보였다.
- 검증 대상이 "컴파일되면 안 된다"인데 gtest로는 표현할 수 없다.
// TODO(crbug.com/40245687): Maybe use the nocompile harness here instead of being "clever"...라고 적어둔 문제를 해결한 경우이다.
해결 내용
어떻게 문제를 해결했는지 설명하세요.
.nc파일로 이전. clang -verify가 expected-error 주석과 실제 진단을 대조한다. 기대한 에러가 안 나도, 예상 못 한 에러가 나도 실패한다.base/threading/sequence_bound_nocompile.nc신규 (43줄),base/BUILD.gn에 등록,sequence_bound_unittest.cc1083~1116행 삭제.sequence_bound.h는 수정하지 않았다. Sequence를 강제하는 장치는 이미 정상 동작한다.
삭제한 것
TYPED_TEST(SequenceBoundTest, NoCompileTests) {
// TODO: Test calling WithArgs() on a method that takes no arguments.
// ... 주석 33줄, 코드 0줄
}
#undef SequenceBound // 이 줄은 유지. 지우면 아래 death test가 깨진다.추가한 것
base/threading/sequence_bound_nocompile.nc 를 신규로 생성하여 chromium의 nocomplie 규칙에 따라 파일을 만들었다.
아래의 코드는 주요내용만 표시하였고 주석이나 파일 컨벤션 내용은 제외하였다.
// `WithArgs()` may only be used with methods that accept arguments.
class NoArgs {
public:
void Method();
};
void CallWithArgsOnMethodTakingNoArgs() {
SequenceBound<NoArgs> sq(SequencedTaskRunner::GetCurrentDefault());
sq.AsyncCall(&NoArgs::Method).WithArgs(1); // expected-error@*:* {{no member named 'WithArgs' in}}
}
// `Then()` may only be used after `WithArgs()`.
class HasArgs {
public:
void Method(int);
};
void CallThenBeforeWithArgs() {
SequenceBound<HasArgs> sq(SequencedTaskRunner::GetCurrentDefault());
sq.AsyncCall(&HasArgs::Method)
.Then(DoNothing()) // expected-error@*:* {{no member named 'Then' in}}
.WithArgs(1);
}테스트 방법
.nc 파일은 바이너리에 링크되지 않는다. wrapper.py가 clang++ -fsyntax-only -Xclang -verify를 돌리고 빈 .o를 만들어 source_set처럼 위장한다. 빌드가 곧 테스트다.
autoninja -C out/Default base_nocompile_tests배운 점
기술
- 디스크 쓰기 같은 느린 작업을 UI 스레드에서 하면 브라우저 화면이 멈춘다. 백그라운드로 보내야 하는데 이 작업을 담당하는 객체를 두 스레드가 동시에 만지면 내부 상태가 깨진다.
Chromium은 이걸 락이 아니라 시퀀스로 푼다. 시퀀스는 작업이 순서대로 실행되는 논리적 흐름이다. 시퀀스 하나에만 갇힌 객체는 락 없이 안전하다. 그 작업순서 실행보장을AsyncCall(필수) → WithArgs(인자있을때 필요) → Then(선택)로 하고 있다. .Then(...).WithArgs(...)는 두 군데가 잘못돼 보이지만 에러는 1개다..Then()에서 표현식이 error type이 되면 뒤는 진단을 내지 않는다. expected-error를 2개 쓰면 테스트가 실패한다.- 에러 메시지는 추측하지 않는다. expected-error 없이 먼저 빌드하면 clang이 실제 메시지를 출력한다.
프로세스
- git blame으로 CL을 찾아 리뷰 코멘트를 읽은 게 도움이 됐다. CL:4004169 에서 dcheng의 description과 patchset을 보고 이 작업의 맥락을 파악하며 나의 작업범위가 무엇인지 알 수 있었다.
- 처음엔 타입 안정성 문제로 봤는데, 직접 컴파일해보니 BindOnce의 arity 검사가 조용히 통과하는 경우는 없었다. 실제 가치는 진단 품질이고 원저자도 리뷰에서 "Better error messages" 라고 해서 작업범위를 결정할 수 있었다.
- 처음엔
SequenceBound<std::unique_ptr<T>>형태까지 4케이스로 썼다. 근거는 "기존 TYPED_TEST가 두 형태를 커버했다"였는데 틀렸었다. 삭제한 테스트는 비어 있어서 어느 형태도 커버하지 않았고, AsyncCall이 빌더를 고를 때 쓰는 템플릿 인자는 전부 메서드 시그니처에서 나온다(sequence_bound.h:227). 두 형태의 UnwrappedT도 같다. 중복이라 패치셋 2에서 제거했다.
향후
- sequence_bound.h:635의 변환 제약을 구현하면 이번에 이관한 TODO를 따라 나머지 테스트를 채울 수 있다.