Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

테스트

Important

디버그 정보 테스트 스위트는 대대적인 재작성이 진행 중입니다. 이 절은 재작성이 진행됨에 따라 채워질 예정입니다.

자세한 내용은 이 추적 이슈를 참고하십시오.

디버그 정보 테스트는 몇 가지 중요한 사항을 검사합니다:

  • 정보를 우리가 예상한 방식으로 출력하고 있습니까?
  • 우리가 출력한 내용을 디버거가 읽을 수 있습니까?
  • 우리의 시각화 도구가 예상한 방식으로 동작합니까?

첫 번째 질문은 보통 tests/codegen-llvm에서 다루지만, 디버그 정보 생성은 의도적으로 테스트되기보다는 부수적으로 테스트되는 경우가 많습니다. 2026년 7월 기준으로 뒤의 두 질문에 훨씬 더 큰 비중을 두고 있으며, 여기에서는 이 두 질문을 자세히 다룹니다. 이 질문들에 답하는 테스트는 tests/debuginfo에 있으며, compiletest가 이를 실행합니다.

테스트 스위트가 존재해 온 기간의 대부분 동안 디버거는 자동으로 탐지되었고, 테스트는 변수 출력, 중단점 위치 등을 검사하는 $DEBUGGER-command 및 $DEBUGGER-check 지시문(즉, 원시 문자열 비교)으로 구성되었습니다. 솔직히 말해 이 시스템은 악몽과도 같았으며 수많은 문제를 초래했습니다. 이를 개선하기 위해 다음과 같은 조치를 취했습니다:

  1. GDB와 LLDB에 대해 tests/debuginfo는 이제 옵트인 방식입니다.
  2. 새로운 지시문 $DEBUGGER-repr이 추가되었습니다. 이 지시문은 출력 결과에 보이지 않는 추가 정보를 디버거에 요청하는 사용자 정의 로직으로 처리를 넘깁니다. 또한 출력을 타깃별로 자동으로 분리하므로, 서로 다른 플랫폼에서 충돌 없이 테스트를 실행할 수 있습니다.

repr 지시문

Important

2026년 7월 현재, 이 명령은 LLDB에서만 지원됩니다. GDB 지원은 계획되어 있으나 아직 구현되지 않았습니다. 이 지시문이 앞으로 CDB에서 사용하기에 적합해질지는 불분명합니다.

요약하면, $DEBUGGER-repr 명령은 다음과 같이 디슈가링됩니다:

//@ $DEBUGGER-command:repr $VAR_NAME
//@ $DEBUGGER-check:$VAR_NAME ok

명령이 디버거에 전달될 때, 테스트 프레임워크는 repr 의사 명령을 가로채 특수 로직을 실행하며, tests/debuginfo/<test_name>/input/<debugger>_input/<target_group>.json에 저장된 데이터와 비교하여 테스트합니다.

“타깃 그룹“은 동일한 출력을 보장할 수 없는 타깃들의 집합을 포괄합니다. 이러한 타깃은 common.py의 Target 열거형에 정의되어 있습니다. 2026년 7월 기준으로 이 목록에는 non_windows, windows_gnu, windows_msvc가 포함됩니다. 각 타깃은 변경이 있을 때마다 갱신해야 하는 새로운 테스트 데이터 집합이므로, 이 목록은 의도적으로 가능한 한 짧게 유지합니다. 모든 타깃에 접근할 수 없는 기여자가 테스트를 --bless하는 방법에 대해서는 아직 완벽한 해결책이 없습니다.

테스트 실행 명령에 --bless를 추가하면(예: ./x test tests/debuginfo/basic-types/main.rs --bless) 예상된 변경 사항에 맞게 입력 데이터를 자동으로 갱신할 수 있습니다. --bless는 메모리 내 표현을 갱신하고, 이를 기준으로 테스트한 뒤, 오류가 발생하지 않으면 데이터를 타깃 파일에 다시 저장합니다(필요한 경우 새 파일을 생성합니다).

입력 데이터의 스키마는 common.py의 클래스들로 정의됩니다. 최상위 컨테이너는 TargetData입니다. 이 스키마는 모든 디버거에서 동일합니다.

기존 테스트 변환하기

변수를 테스트할 때는 거의 언제나 command + check보다 repr 지시문을 사용하는 것이 좋습니다. 2026년 7월 기준으로 변환된 테스트는 하나뿐이지만, 앞에서 언급한 테스트 재작성 과정에서 더 많은 테스트가 변환될 예정입니다. 다행히 변환 과정은 꽤 간단합니다. 다음과 같은 검사가 있다면:

//@ lldb-command:v foo
//@ lldb-check:<foo output>

이에 해당하는 repr 테스트는 다음과 같습니다:

//@ lldb-repr:foo

모든 command + checks를 repr로 변환했다면 --bless 옵션으로 테스트를 실행하십시오. 다른 타깃도 사용할 수 있다면 나머지 타깃 그룹의 데이터도 --bless하십시오(예를 들어 Windows 머신을 사용한다면 x86_64-pc-windows-msvc에 대해 한 번, x86_64-pc-windows-gnu에 대해 한 번 bless하고, WSL을 사용하여 x86_64-unknown-linux-gnu에 대해서도 bless하십시오).

구현

직렬화/역직렬화

TargetData는 dataclasses.asdict를 통해 딕셔너리로 변환된 후 Python의 내장 JSON 라이브러리로 직렬화됩니다. 테스트할 때는 데이터를 dict로 읽어 들여 TargetData로 변환한 뒤 최상위 INPUT_DATA 변수에 저장합니다. 현재의 역직렬화 로직은 스키마가 변경되어도 문제없이 동작해야 하지만, 모든 필드가 json.dumps로 직접 직렬화/역직렬화할 수 있는 타입“만“ 포함해야 합니다. 허용되는 타입은 common.JsonType을 구성하는 타입들입니다.

직렬화/역직렬화가 디버거 로직과 분리되어 있으므로, json보다 더 나은 대안을 찾으면 다른 형식으로 쉽게 전환할 수 있습니다.

디버거의 내부 표현을 스키마 클래스로 변환하는 로직은 from_$DEBUGGER.py에 있습니다.

common은 임포트되면 기존 입력 데이터를 자동으로 역직렬화하여 전역 변수 INPUT_DATA에 저장합니다. 테스트는 이 데이터를 기준으로 수행됩니다.

Note

repr 명령이 실제로 처리되지 않는 한 lldb_batchmode가 common을 임포트하지 않도록 특별히 주의를 기울였습니다. 덕분에 입력 데이터가 필요 없는 테스트에서는 데이터를 읽거나 쓰지 않아도 됩니다.

형식 세부 사항

디버그 세션이 시작된 후에는 타입 정보가 고유하며 변하지 않으므로, 타입은 최상위에 한 번만 저장되고 그 밖의 모든 곳에서는 이름으로 참조됩니다.

포인터 값은 실행할 때마다 달라집니다. 불일치를 막기 위해 포인터 변수는 값을 저장하지 않습니다. 이는 -check 지시문에서 사용하는 와일드카드 [...]와 같습니다.

BlessMetadata는 TargetData에 포함되지만 테스트 대상은 아닙니다. 이는 오직 테스트 데이터가 어떻게 생성되었는지를 기록하기 위해 존재하며, Python이나 디버거의 버전 변경으로 발생할 수 있는 문제를 진단하는 데 도움을 줍니다.

진입점과 --bless

repr 의사 명령을 만나면 lldb_batchmode.main은 check_$DEBUGGER.check()로 처리를 넘깁니다. --bless 옵션이 지정된 경우, 변수는 메모리 내 표현에서 이에 대응하는 스키마 클래스로 변환됩니다. 여기에는 변수의 타입, 시각화 도구, 자식, 자식의 타입 등이 포함됩니다. TargetData에 삽입되고 나면, 변수는 방금 TargetData에 저장된 데이터와 비교하여 테스트됩니다.

예외나 오류가 발생하지 않았고 --bless 옵션이 지정된 경우, lldb_batchmode가 종료되기 직전에 INPUT_DATA가 적절한 파일에 기록됩니다. 오류가 발생하면 INPUT_DATA는 그냥 폐기됩니다.

현재 repr 의사 명령은 직접 검사하여 식별합니다. GDB와 LLDB는 모두 Python 코드를 통해 사용자 정의 CLI 명령을 만드는 기능을 지원합니다. 향후에는 repr이 두 디버거 중 하나 또는 둘 모두의 CLI 명령으로 구현될 수 있습니다.

검사 로직

check_$DEBUGGER.check는 디버거의 변수 객체를 Variable 객체로 변환한 뒤 둘을 비교합니다. 불일치가 발견되면 오류를 더 유용한 방식으로 보고하기 위해 추가 처리를 수행합니다. 즉, 오류는 발생하는 즉시 감지되고 보고되며, 여기에는 여러 가지 장점이 있습니다. 가장 중요한 점은, 실패 이후 디버거 상태가 바뀌지 않았고 디버거의 변수 객체에 여전히 접근할 수 있으므로, 디버거에 추가 정보를 요청하여 더 유용한 오류 메시지를 제공할 수 있다는 것입니다.

예를 들어, LLDB는 synthetic/summary 프로바이더 호출 내에서 발생하는 오류를 잘 드러내지 않는 경향이 있습니다. 이는 다른 명령 안에서 명령을 실행할 때 특히 그러한데, 테스트가 바로 script import lldb_batchmode; lldb_batchmode.main()을 호출하고 그 컨텍스트에서 명령을 실행하는 방식으로 동작합니다.

오류를 만나면 적절한 summary 프로바이더를 임포트하여 변수 객체를 전달하고, 예외를 직접 출력할 수 있습니다. 또한 합성 프로바이더 클래스를 검사하여 필수 함수를 모두 구현하고 있는지 확인할 수도 있습니다.

오류가 발생해도 테스트가 즉시 종료되지 않습니다. 이는 --bless 옵션이 추가된 지금 특히 중요합니다. --bless는 모든 입력 데이터를 갱신하므로, 읽는 사람이 추가로 변경해야 할 사항이 있는지 충분한 정보를 바탕으로 판단할 수 있도록 모든 오류를 출력해야 합니다. 첫 번째 오류가 우연히 예상된 변경이었다는 이유만으로 사람들이 잘못된 데이터를 실수로 승인(bless)하는 일은 절대 없어야 합니다.

오류는 repr 의사 명령의 가시적인 출력으로 나타나도록 stdout에 직접 출력됩니다. 형식을 일관되게 유지하기 위한 여러 오류 도우미 함수가 있습니다.

Note

LLDB는 script 명령을 실행할 때 Python 인터프리터의 stderr를 출력하지 않습니다. 인터프리터가 예외와 함께 종료되면 그 예외는 출력하지만, stderr의 나머지 내용은 전혀 출력하지 않습니다. 대신 stderr에 출력하고 싶다면, Python io.TextIoWrapper를 반환하는 lldb.debugger.GetErrorFileHandle을 호출하여 디버거의 것을 사용할 수 있습니다.

특정 변수에 대해 오류가 발생하지 않았다면, compiletest가 대조할 수 있도록 $VAR_NAME ok가 stdout에 출력됩니다.

lldb_batchmode가 종료되기 전에, INPUT_DATA에 있던 모든 타입과 변수가 검사되었는지 확인하는 마지막 검사가 수행됩니다. 이 검사가 실패하면 스크립트는 테스트되지 않은 타입/변수를 보고하고 오류 코드와 함께 종료합니다.

LLDB 버전 관리

Apple은 Swift 지원이 포함된 LLDB 포크를 Xcode와 함께 배포합니다. 이 LLDB 포크는 LLVM의 LLDB와 같은 버전 체계를 사용하지 않습니다:

# Apple:
lldb-1703.0.236.21 Apple Swift version 6.2.3 (swiftlang-6.2.3.3.21 clang-1700.6.3.2)
# LLVM:
lldb version 22.1.2 (https://github.com/llvm/llvm-project revision 1ab49a973e210e97d61e5db6557180dcb92c3e98)
  clang revision 1ab49a973e210e97d61e5db6557180dcb92c3e98
  llvm revision 1ab49a973e210e97d61e5db6557180dcb92c3e98

Apple LLDB의 버전은 LLVM의 버전에서 파생된 것으로 보이지 않으므로, 둘 사이를 쉽게 또는 자동으로 변환할 수 없습니다. 다행히도 Swift LLVM 저장소에서 해당하는 릴리스 브랜치를 확인하면 기반 LLVM 버전을 수동으로 확인할 수는 있습니다. 위 예시의 경우 Swift 6.2.3에 대한 브랜치는 존재하지 않지만, 6.2.2에 대한 브랜치는 있습니다. LLVM 버전은 llvm/utils/gn/secondary/llvm/version.gni에 있습니다. 해당 예시에서 볼 수 있듯이, 위의 Apple LLDB 버전은 (대략) LLVM LLDB 19.1.5에 해당합니다.

이를 통해 CI에서 사용되는 Apple LLDB에서 어떤 기능을 사용할 수 있는지 더 잘 파악할 수 있으므로, 문제를 진단하거나 새 테스트를 작성할 때 유용할 수 있습니다. 예를 들어 LLDB 19는 Type Recognizer 함수를 지원한 최초의 버전이었으므로, 예시로 든 Apple LLDB가 이를 지원한다고 가정할 수 있습니다.