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

Compiletest

소개

compiletest는 Rust 테스트 스위트의 주요 테스트 하네스입니다. 테스트 작성자는 이를 통해 많은 수의 테스트를 체계적으로 구성하고(Rust 컴파일러에는 수천 개의 테스트가 있습니다), 테스트를 효율적으로 실행할 수 있으며(병렬 실행을 지원합니다), 개별 테스트와 테스트 그룹 모두의 동작과 예상 결과를 설정할 수 있습니다.

macOS 사용자를 위한 참고 사항

macOS에서는 SIP(System Integrity Protection)가 Apple에 네트워크 요청을 보내 컴파일된 바이너리를 매번 검사할 수 있으므로, 테스트를 실행할 때 성능이 크게 저하될 수 있습니다.

다음 설정을 조정하여 이 문제를 해결할 수 있습니다: Privacy & Security -> Developer Tools -> Add Terminal (Or VsCode, etc.).

compiletest는 테스트 코드가 컴파일 타임 또는 런타임에 성공하는지 실패하는지 검사할 수 있습니다.

테스트는 일반적으로 Rust 소스 파일로 구성되며, 테스트 코드 앞이나 안(또는 둘 다)에 있는 주석에 어노테이션을 담습니다. 이러한 주석은 테스트를 실행할지 여부와 실행 방법, 예상되는 동작 등을 compiletest에 지시하는 역할을 합니다. 이러한 어노테이션에 대한 자세한 내용은 지시문과 아래의 테스트 스위트 문서를 참고하십시오.

새 테스트를 작성하는 튜토리얼과 좋은 테스트를 작성하기 위한 조언은 새 테스트 추가하기 장과 모범 사례 장을, 테스트 스위트를 실행하는 방법은 테스트 실행하기 장을 참고하십시오.

인자는 --test-args를 사용하거나 -- 뒤에 배치하여 compiletest에 전달할 수 있습니다. 예를 들면 다음과 같습니다.

  • x test --test-args --force-rerun
  • x test -- --force-rerun

또한 bootstrap은 몇 가지 일반적인 인자를 직접 받습니다. 예를 들면 다음과 같습니다.

x test --no-capture --force-rerun --run --pass.

Compiletest 자체는 관련된 산출물(주로 컴파일러)이 변경되지 않았을 때 테스트 실행을 피하려고 합니다. 입력이 전혀 변경되지 않았을 때에도 테스트를 다시 실행하려면 x test --test-args --force-rerun을 사용하면 됩니다.

테스트 스위트

모든 테스트는 tests 디렉터리에 있습니다. 테스트는 “스위트“로 구성되며, 각 스위트는 별도의 하위 디렉터리에 있습니다. 테스트 스위트마다 컴파일러 동작과 정확성 검사 방식이 달라서 각각 조금씩 다르게 동작합니다. 예를 들어 tests/incremental 디렉터리에는 증분 컴파일 테스트가 들어 있습니다. 여러 스위트는 src/tools/compiletest/src/common.rs의 pub enum Mode 선언에 정의되어 있습니다.

사용할 수 있는 테스트 스위트는 다음과 같으며, 자세한 내용은 링크를 참고하십시오.

컴파일러 전용 테스트 스위트

테스트 스위트목적
ui컴파일 결과 또는 생성된 실행 파일의 실행 결과(또는 둘 다)에서 나온 stdout/stderr 스냅숏을 검사합니다
ui-fulldepsextern crate rustc_span;을 사용하거나 플러그인으로 사용되는 등, 링크 가능한 rustc 빌드가 필요한 ui 테스트입니다
pretty예쁜 출력(pretty printing)을 확인합니다
incremental증분 컴파일 동작을 확인합니다
debuginfo디버거를 실행하여 디버그 정보 생성을 확인합니다
codegen-*코드 생성을 확인합니다
codegen-units코드 생성 단위 분할을 확인합니다
assembly어셈블리 출력을 확인합니다
mir-optMIR 생성 및 최적화를 확인합니다
coverage커버리지 계측을 확인합니다
coverage-run-rustdoc계측된 문서 테스트도 함께 실행하는 coverage 테스트입니다
crashes의도치 않은 수정을 잡아내기 위해 특정 입력에서 컴파일러가 ICE/패닉/크래시를 일으키는지 확인합니다

범용 테스트 스위트

run-make는 Rust 프로그램을 사용하는 범용 테스트입니다.

build-std(표준 라이브러리 빌드) 테스트 스위트

build-std는 -Zbuild-std가 동작하는지 확인하는 테스트입니다.

Rustdoc 테스트 스위트

테스트 스위트목적
rustdoc-htmlrustdoc의 HTML 출력을 확인합니다
rustdoc-gui웹 브라우저를 사용하여 rustdoc의 GUI를 확인합니다
rustdoc-jsrustdoc의 검색 엔진과 인덱스를 검사합니다
rustdoc-js-stdstd 라이브러리 문서에서 rustdoc의 검색 엔진과 인덱스를 검사합니다
rustdoc-jsonrustdoc의 JSON 출력을 검사합니다
rustdoc-uirustdoc의 터미널 출력을 검사합니다(참고)

ui/rustdoc/에도 rustdoc 전용 테스트가 일부 들어 있습니다. 이 테스트들은 rustdoc 실행 중에 발생하는 특정 린트가 rustc를 실행할 때도 실행되는지 확인합니다.

run-make/rustdoc/에는 rustdoc과 관련된 run-make 테스트가 들어 있습니다.

프리티 프린터 테스트

tests/pretty의 테스트는 rustc의 “프리티 프린팅” 기능을 검증합니다. rustc의 -Z unpretty CLI 옵션은 입력 소스를 매크로 확장 이후의 Rust 소스 등 다양한 형식으로 변환하게 합니다.

프리티 프린터 테스트에는 아래에서 설명하는 여러 지시문이 있습니다. 이 명령들은 테스트의 동작을 크게 바꿀 수 있지만, 명령이 없을 때의 기본 동작은 다음과 같습니다.

  1. 소스 파일에 대해 rustc -Zunpretty=normal을 실행합니다.
  2. 이전 단계의 출력에 대해 rustc -Zunpretty=normal을 실행합니다.
  3. 앞의 두 단계의 출력은 같아야 합니다.
  4. 출력에 대해 rustc -Zno-codegen을 실행하여 타입 검사가 가능한지 확인합니다(cargo check와 비슷합니다).

위 명령 중 하나라도 실패하면 테스트가 실패합니다.

프리티 프린팅 테스트의 지시문은 다음과 같습니다.

  • pretty-mode는 pretty-print 테스트를 실행할 모드(즉, -Zunpretty에 전달할 인자)를 지정합니다. 지정하지 않으면 기본값은 normal입니다.
  • pretty-compare-only를 사용하면 프리티 테스트가 프리티 프린트된 출력만 비교합니다(위의 3단계까지만 수행하고 멈춥니다). 전개된 출력을 컴파일하여 타입 검사를 시도하지는 않습니다. 이는 유효한 Rust로 전개되지 않는 pretty-mode나, 전개된 출력을 컴파일할 수 없는 그 밖의 상황에 필요합니다.
  • pp-exact는 프리티 프린트 테스트가 특정 출력을 내도록 보장하는 데 사용합니다. 값 없이 지정하면 프리티 프린트 출력이 원본 소스와 일치해야 한다는 뜻입니다. //@ pp-exact:foo.pp처럼 값과 함께 지정하면 프리티 프린트된 출력이 지정한 파일의 내용과 일치하는지 확인합니다. 그렇지 않고 pp-exact를 지정하지 않으면, 프리티 프린트된 출력을 한 번 더 프리티 프린트한 뒤 두 번의 프리티 프린팅 결과를 비교하여 프리티 프린트된 출력이 안정된 상태로 수렴하는지 확인합니다.

증분 테스트

tests/incremental의 테스트는 증분 컴파일을 검사합니다. 이 테스트는 revisions 지시문을 사용하여 compiletest가 컴파일러를 여러 단계에 걸쳐 실행하도록 지시합니다.

Compiletest는 -C incremental 플래그를 사용해 빈 디렉터리에서 시작한 다음, 각 리비전마다 이전 단계의 증분 결과를 재사용하면서 컴파일러를 실행합니다.

각 리비전 이름은 다음 중 하나로 시작해야 합니다:

  • cpass - 테스트가 성공적으로 컴파일되어야 합니다(체크 빌드, 코드 생성 없음).
  • bfail — 테스트가 컴파일에 실패해야 합니다(전체 빌드, 코드 생성 포함).
  • bpass — 테스트가 성공적으로 컴파일되어야 합니다(전체 빌드, 코드 생성 포함).
  • rpass — 테스트가 성공적으로 컴파일되고 실행되어야 합니다.

리비전을 고유하게 만들려면 rpass1, rpass2와 같은 접미사를 붙여야 합니다.

소스 변경을 시뮬레이션하기 위해 compiletest는 현재 리비전 이름을 담은 --cfg 플래그도 전달합니다.

예를 들어, 다음 테스트는 함수 변경을 시뮬레이션하면서 두 번 실행됩니다:

//@ revisions: rpass1 rpass2

#[cfg(rpass1)]
fn foo() {
    println!("one");
}

#[cfg(rpass2)]
fn foo() {
    println!("two");
}

fn main() { foo(); }

증분 테스트는 특정 부분 문자열이 컴파일러 출력 어디에도 나타나서는 안 된다는 것을 지정하는 forbid-output 지시문을 지원합니다. 이는 특정 오류가 나타나지 않음을 보장하는 데 유용할 수 있지만, 오류 메시지는 시간이 지나면서 바뀌므로 깨지기 쉽습니다. 또한 테스트가 더 이상 올바른 대상을 검사하지 않는데도 계속 통과할 수 있습니다.

증분 테스트에서는 #[rustc_clean(...)] 속성을 사용할 수 있습니다. 이 속성은 현재 컴파일 세션의 핑거프린트를 이전 세션의 핑거프린트와 비교합니다. 첫 번째 리비전은 항상 dirty 상태이므로 활성화된 rustc_clean 속성을 절대 가져서는 안 됩니다.

기본 모드에서는 핑거프린트가 같아야 함을 단언합니다. 이 속성은 다음 인자를 받습니다.

  • cfg="<cond>" — cfg 조건 <cond>를 확인하고, 해당 설정 조건이 참으로 평가될 때에만 검사를 실행합니다. 이를 사용하여 특정 리비전에서만 rustc_clean 속성을 실행할 수 있습니다.
  • except="<query1>,<query2>,..." — 나열된 쿼리의 쿼리 결과가 같지 않고 달라야 함을 단언합니다.
  • loaded_from_disk="<query1>,<query2>,..." — 나열된 쿼리의 쿼리 결과가 (단순히 green으로 표시된 것이 아니라) 실제로 디스크에서 로드되었음을 단언합니다. 이는 테스트가 특정 쿼리 결과에 대한 역직렬화 로직을 실제로 검사하고 있는지 확인하는 데 유용할 수 있습니다. 이 인자는 except와 함께 사용할 수 있습니다.

rustc_clean을 사용하는 테스트의 간단한 예로는 hello_world 테스트가 있습니다.

Debuginfo 테스트

Important

#159455부터 이 테스트는 옵트인 방식으로 바뀌었습니다. 자세한 맥락은 다음을 참고하십시오: debuginfo 테스트 스위트 상태 안정화

tests/debuginfo의 테스트는 지원되는 디버거가 debuginfo를 어떻게 해석하는지 테스트하고, 시각화가 여전히 예상대로 작동하는지 확인합니다. 이 테스트는 프로그램을 빌드하고, 디버거를 실행한 뒤, 디버거에 명령을 내립니다. 하나의 테스트가 cdb, gdb, lldb에서 모두 동작할 수 있습니다.

대부분의 테스트에는 적절한 debuginfo를 생성하기 위해 //@ compile-flags: -g 지시문 또는 이와 비슷한 지시문이 있어야 합니다.

특정 줄에 중단점을 설정하려면 해당 줄에 // #break 주석을 추가하십시오.

debuginfo 테스트는 일련의 디버거 명령과, 디버거가 출력할 것으로 예상되는 내용을 지정하는 “check” 줄로 구성됩니다.

명령은 //@ $DEBUGGER-command:$COMMAND 형식의 주석이며, 여기서 $DEBUGGER는 사용 중인 디버거이고 $COMMAND는 실행할 디버거 명령입니다.

디버거 값은 다음 중 하나일 수 있습니다.

  • cdb
  • gdb
  • lldb

출력을 확인하는 명령은 //@ $DEBUGGER-check:$OUTPUT 형식이며, 여기서 $OUTPUT은 예상되는 출력입니다.

예를 들어, 다음은 테스트를 빌드하고, 디버거를 시작하고, 중단점을 설정하고, 프로그램을 실행하고, 값을 검사한 뒤, 디버거가 출력하는 내용을 확인합니다.

//@ compile-flags: -g

//@ lldb-command: run
//@ lldb-command: print foo
//@ lldb-check: $0 = 123

fn main() {
    let foo = 123;
    b(); // #break
}

fn b() {}

또한 단순한 문자열 비교로 가능한 것보다 더 세밀하게 변수(및 그 시각화 도구)를 검증하기 위한 특수 명령인 //@ $DEBUGGER-repr:$VAR_NAME이 있습니다. 가능하면 언제나 -command/-check보다 이 지시문을 우선적으로 사용하십시오.

Note

이 글을 작성하는 시점(2026년 7월)에 이 명령은 LLDB에서만 사용할 수 있으며, GDB용 구현이 곧 제공될 예정입니다. 이 로직을 CDB로 이식할 확정된 계획은 없습니다.

이 명령은 사실상 다음과 같이 디슈가링됩니다.

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

repr $VAR_NAME 명령은 디버거의 API를 사용하여 변수의 출력 결과에 반영되지 않는 데이터를 검사하는 특수 로직이 가로챕니다. 메모리상의 변수는 tests/debuginfo/<test_name>/input/<debugger>_input/<target_group>.json에 저장된 입력 데이터와 비교되며, 실패 시 상세한 오류 메시지를 제공합니다.

Important

-repr 지시문은 -command/-check와 달리 --bless 옵션과 호환됩니다. -repr 명령이 있는 파일을 --bless하면 해당 대상의 입력 데이터 파일이 자동으로 생성/갱신됩니다.

이 명령의 구현 세부 사항은 디버그 정보 장의 테스트 절에서 더 자세히 설명합니다.

현재 사용 중인 디버거에 따라 테스트를 비활성화하는 데 다음 지시문을 사용할 수 있습니다.

  • min-cdb-version: 10.0.18317.1001 — cdb 버전이 지정된 버전보다 낮으면 테스트를 무시합니다
  • min-gdb-version: 8.2 — gdb 버전이 지정된 버전보다 낮으면 테스트를 무시합니다
  • ignore-gdb-version: 9.2 — gdb 버전이 지정된 버전과 같으면 테스트를 무시합니다
  • ignore-gdb-version: 7.11.90 - 8.0.9 — gdb 버전이 범위(양 끝 포함) 안에 있으면 테스트를 무시합니다
  • min-apple-lldb-version: 1703.0.236.21/min-llvm-lldb-version: 21.1.0 — lldb 버전이 지정된 버전보다 낮으면 테스트를 무시합니다. 참고: (Xcode와 함께 배포되는) Apple의 LLDB 포크는 LLVM의 LLDB 버전 번호에 쉽게 대응시킬 수 없는 별도의 버전 체계를 사용합니다. 따라서 버전 조건은 배포사별로 지정합니다. 버전 대응 관계를 수동으로 확인하는 방법에 대한 자세한 정보는 여기에서 확인할 수 있습니다
  • rust-lldb — lldb에 Rust 플러그인이 포함되어 있지 않으면 테스트를 무시합니다. 참고: LLDB의 “Rust” 버전은 더 이상 존재하지 않으므로 이 테스트는 항상 무시됩니다. 이 지시어는 제거하는 것이 좋을 것입니다.

compiletest에 --debugger 옵션을 전달하면 테스트를 실행할 디버거를 하나만 지정할 수 있습니다. 예를 들어, ./x test tests/debuginfo -- --debugger gdb는 GDB 명령만 테스트합니다.

미리 빌드된 lldb 배포판으로 lldb debuginfo 테스트를 로컬에서 실행할 때의 참고 사항

lldb debuginfo 테스트를 로컬에서 실행하려면 현재 Windows에서는 다음 조건을 갖추어야 합니다.

LLDB 21

  • Python 3.10이 설치되어 있어야 합니다.
  • PATH 환경 변수를 통해 python310.dll을 사용할 수 있어야 합니다. 이 파일은 python.org에서 받는 표준 Python 설치 프로그램에 포함되어 있지 않으므로 직접 PATH에 추가해야 합니다.

LLDB 22

  • Python 3.11이 설치되어 있어야 합니다.
  • PATH를 통해 python311.dll을 사용할 수 있어야 합니다.
  • Python은 pymanager로 설치하기를 권장합니다. pymanager에는 PATH 설정을 도와주는 후속 설치 스크립트가 있습니다.

그렇지 않으면 lldb debuginfo 테스트가 원인을 알 수 없는 크래시를 일으킬 수 있습니다.

Windows 11에서 cdb.exe를 설치할 때의 참고 사항

cdb.exe는 적절한 “Windows 11 SDK“와 함께 설치됩니다. 이 SDK는 Visual Studio 설치 프로그램(예: Visual Studio 2022 설치 프로그램)의 “C++를 사용한 데스크톱 개발”(Desktop Development with C++) 워크로드 프로필에 포함되어 있습니다.

그러나 기본 설정만으로는 충분하지 않습니다. cdb.exe가 필요하다면 설치된 앱으로 이동하여 가장 최신의 “Windows Software Development Kit“을 찾아야 합니다(운영 체제 이름이 Windows 11이더라도 이 항목에는 여전히 Windows 10.0.22161.3233이라고 표시될 수 있습니다). 그런 다음 cdb.exe를 설치하려면 “수정”(Modify) -> “변경”(Change)을 클릭하고 “Debugging Tools for Windows“를 선택해야 합니다.

코드 생성 테스트

tests/codegen-llvm의 테스트는 LLVM 코드 생성을 테스트합니다. 이 테스트들은 --emit=llvm-ir 플래그로 테스트를 컴파일하여 LLVM IR을 내보냅니다. 그런 다음 LLVM FileCheck 도구를 실행합니다. 테스트에는 생성된 코드를 검사하는 다양한 // CHECK 주석이 달려 있습니다. 튜토리얼과 자세한 정보는 FileCheck 문서를 참고하십시오.

비슷한 테스트 모음으로 어셈블리 테스트도 참고하십시오.

기본적으로 코드 생성 테스트에는 테스트 소스에 #![no_std]/#![no_core] 속성이 지정되지 않은 한 //@ needs-target-std(대상이 std를 지원해야 함)가 암묵적으로 적용됩니다. 이 동작을 재정의하여 //@ needs-target-std를 명시적으로 작성하면 테스트가 #![no_std]/#![no_core]이더라도 대상이 std를 지원할 때만 테스트가 실행됩니다.

#![no_std] 크로스 컴파일 테스트를 다루어야 한다면 minicore 테스트 보조 도구 장을 참고하십시오.

어셈블리 테스트

tests/assembly-llvm의 테스트는 LLVM 어셈블리 출력을 테스트합니다. 이 테스트들은 --emit asm 플래그로 테스트를 컴파일하여 어셈블리 출력이 담긴 .s 파일을 내보냅니다. 그런 다음 LLVM FileCheck 도구를 실행합니다.

각 테스트에는 어셈블리 출력의 종류를 나타내는 //@ assembly-output: 지시문을 emit-asm 또는 ptx-linker 값과 함께 달아야 합니다.

그런 다음 어셈블리 출력을 검사할 수 있도록 테스트에 여러 // CHECK 주석을 달아야 합니다. 튜토리얼과 자세한 내용은 FileCheck 문서를 참고하십시오.

비슷한 테스트 모음으로 코드 생성 테스트도 참고하십시오.

#![no_std] 크로스 컴파일 테스트를 다뤄야 한다면 minicore 테스트 보조 장을 참고하십시오.

코드 생성 단위(codegen-units) 테스트

tests/codegen-units의 테스트는 단형화 수집기와 CGU 분할을 테스트합니다.

이 테스트는 단형화 수집 패스의 결과를 출력하는 플래그인 -Zprint-mono-items를 붙여 rustc를 실행한 다음, 파일 안의 특수 주석을 그 결과와 비교하는 방식으로 동작합니다.

그런 다음 테스트에 //~ MONO_ITEM name 형식의 주석을 달아야 합니다. 여기서 name은 fn <u32 as Trait>::foo처럼 rustc가 출력하는 단형화된 문자열입니다.

CGU 분할을 검사하려면 //~ MONO_ITEM name @@ cgu 형식의 주석을 사용합니다. 여기서 cgu는 CGU 이름과 대괄호로 묶은 링키지 정보를 공백으로 구분한 목록입니다. 예: //~ MONO_ITEM static function::FOO @@ statics[Internal]

Mir-opt 테스트

tests/mir-opt의 테스트는 생성된 MIR의 일부를 검사하여 MIR이 올바르게 생성되는지, 기대한 최적화가 이루어지는지 확인합니다. 자세한 내용은 MIR 최적화 장을 참고하십시오.

Compiletest는 MIR 출력을 덤프하고 최적화 기준선을 설정하기 위해 다음 플래그들을 붙여 테스트를 빌드합니다:

  • -Copt-level=1
  • -Zdump-mir=all
  • -Zmir-opt-level=4
  • -Zvalidate-mir
  • -Zdump-mir-exclude-pass-number

테스트에는 기대하는 MIR 출력을 담을 파일을 지정하는 // EMIT_MIR 주석을 달아야 합니다. x test --bless를 사용하여 초기 기대 파일을 생성할 수 있습니다.

EMIT_MIR 주석은 다음과 같은 여러 형식으로 작성할 수 있습니다:

  • // EMIT_MIR $MIR_PATH.mir — 주어진 파일이 MIR 덤프의 출력과 정확히 일치하는지 검사합니다. 예를 들어 my_test.main.SimplifyCfg-elaborate-drops.after.mir는 테스트 디렉터리에서 해당 파일을 불러와 rustc의 덤프와 비교합니다.

    최적화 후의 최종 상태에 관심이 있다면 “after” 파일(최적화 이후의 파일)을 검사하는 것이 유용합니다. 드물게 완전성을 위해 “before” 파일을 사용하고 싶은 경우도 있습니다.

  • // EMIT_MIR $MIR_PATH.diff — 여기서 $MIR_PATH는 my_test_name.my_function.EarlyOtherwiseBranch와 같은 MIR 덤프의 파일 이름입니다. Compiletest는 .before.mir 파일과 .after.mir 파일의 diff를 구하고, 그 diff 출력을 EMIT_MIR 주석에 지정된 기대 .diff 파일과 비교합니다.

    이 형식은 최적화가 MIR을 어떻게 바꾸는지 보고 싶을 때 유용합니다.

  • // EMIT_MIR $MIR_PATH.dot — 추가 MIR 데이터를 덤프하는 특정 플래그(예: .dot 파일을 생성하는 -Z dump-mir-graphviz)를 사용할 때, 출력이 주어진 파일과 일치하는지 검사합니다.

기본적으로 32비트 타깃과 64비트 타깃은 같은 덤프 파일을 사용하므로, 상수 안에 포인터가 있거나 그 밖에 비트 폭에 의존하는 요소가 있으면 문제가 될 수 있습니다. 그런 경우 테스트에 // EMIT_MIR_FOR_EACH_BIT_WIDTH를 추가하면 32비트 시스템용 파일과 64비트 시스템용 파일이 따로 생성됩니다.

run-make 테스트

tests/run-make 및 tests/run-make-cargo의 테스트는 Rust 레시피를 사용하는 범용 테스트입니다. 레시피는 rustc 호출 같은 임의의 Rust 코드를 실행할 수 있는 작은 프로그램(rmake.rs)이며, run_make_support 라이브러리의 지원을 받습니다. Rust 레시피를 사용하면 최고 수준의 유연성을 얻을 수 있습니다.

요구 사항에 더 잘 맞는 다른 테스트 스위트가 없을 때 run-make 테스트를 사용하십시오.

run-make-cargo 테스트 스위트는 트리 내 cargo를 트리 내 rustc와 함께 테스트해야 하는 사용 사례를 지원하기 위해 트리 내 cargo를 추가로 빌드합니다. run-make 테스트 스위트는 트리 내 cargo에 접근할 수 없습니다(그래서 더 빠르게 반복할 수 있는 테스트 스위트가 될 수 있습니다).

build-std 테스트

tests/build-std의 테스트는 -Zbuild-std가 제대로 작동하는지 확인합니다. 현재 이 테스트 스위트는 레시피가 하나뿐인 run-make 테스트 스위트일 뿐입니다. 이 레시피는 테스트 케이스를 생성하여 병렬로 실행합니다.

Rust 레시피 사용하기

각 테스트는 레시피라고 부르는 rmake.rs Rust 프로그램과 함께 별도의 디렉터리에 있어야 합니다. 레시피는 run_make_support 라이브러리가 링크된 상태로 compiletest에 의해 컴파일되고 실행됩니다.

새로운 유틸리티나 기능이 필요하다면 run_make_support 라이브러리를 확장하고 개선하는 것을 고려하십시오.

UI 테스트와 마찬가지로 rmake.rs에서도 //@ only-<target>이나 //@ ignore-<target> 같은 compiletest 지시문을 지원합니다. 하지만 리비전이나 지시문을 통한 보조 파일 빌드는 현재 지원되지 않습니다.

rmake.rs와 run-make-support는 베타 버전이거나 심지어 안정 버전일 수도 있는 stage 0 rustc로 컴파일할 수 있어야 하므로, nightly/불안정 기능을 사용해서는 안 됩니다.

기본적으로 run-make 테스트는 각 하위 프로세스 명령과 해당 stdout/stderr를 출력합니다. panic=abort 테스트 스위트(예: cg_clif)에서 --no-capture와 함께 실행하면 이 출력이 터미널을 가득 채울 수 있습니다. 통과한 테스트의 이 출력을 숨기려면 --verbose-run-make-subprocess-output을 생략하십시오. 실패한 테스트는 이와 관계없이 항상 출력합니다:

./x test tests/run-make --no-capture --verbose-run-make-subprocess-output=false

rmake.rs 테스트를 컴파일할 수 있는지 빠르게 확인하기

rmake.rs를 stage0 컴파일러로 강제로 컴파일하면, stage1 rustc를 빌드하지 않고도 rmake.rs 테스트를 컴파일할 수 있는지 빠르게 확인할 수 있습니다:

$ COMPILETEST_FORCE_STAGE0=1 x test --stage 0 tests/run-make/<test-name>

물론 일부 테스트는 이 방법으로는 제대로 실행되지 않습니다.

rmake.rs에서 rust-analyzer 사용하기

다른 테스트 프로그램과 마찬가지로, run-make 테스트에서 사용하는 rmake.rs 스크립트에는 기본적으로 rust-analyzer 통합이 없습니다.

특정 테스트를 작업할 때 이 문제를 해결하려면, 해당 테스트의 디렉터리에 다음 내용으로 Cargo.toml 파일을 임시로 만드십시오(예: tests/run-make/sysroot-crates-are-unstable/Cargo.toml):

이 Cargo.toml이나 해당 Cargo.lock을 실제 PR에 추가하지 않도록 주의하십시오!

# Convince cargo that this isn't part of an enclosing workspace.
[workspace]

[package]
name = "rmake"
version = "0.1.0"
edition = "2021"

[dependencies]
run_make_support = { path = "../../../src/tools/run-make-support" }

[[bin]]
name = "rmake"
path = "rmake.rs"

그런 다음 "rust-analyzer.linkedProjects"에 해당 항목을 추가하십시오(예: .vscode/settings.json에서):

"rust-analyzer.linkedProjects": [
  "tests/run-make/sysroot-crates-are-unstable/Cargo.toml"
],

커버리지 테스트

tests/coverage의 테스트는 커버리지 계측을 서로 다른 방식으로 테스트하는 여러 테스트 모드에서 공유됩니다. coverage 테스트 스위트를 실행하면 각 테스트가 모든 커버리지 모드에서 자동으로 실행됩니다.

각 모드에는 해당 모드에서만 커버리지 테스트를 실행하는 별칭도 있습니다:

./x test coverage # runs all of tests/coverage in all coverage modes
./x test tests/coverage # same as above

./x test tests/coverage/if.rs # runs the specified test in all coverage modes

./x test coverage-map # runs all of tests/coverage in "coverage-map" mode only
./x test coverage-run # runs all of tests/coverage in "coverage-run" mode only

./x test coverage-map -- tests/coverage/if.rs # runs the specified test in "coverage-map" mode only

어떤 이유로 특정 테스트를 커버리지 테스트 모드 중 하나에서 실행하지 않아야 한다면 //@ ignore-coverage-map 또는 //@ ignore-coverage-run 지시문을 사용하십시오.

coverage-map 스위트

coverage-map 모드에서 이 테스트들은 소스 코드 영역과 LLVM이 내보내는 커버리지 카운터 사이의 매핑을 검증합니다. 이 테스트들은 --emit llvm-ir로 테스트를 컴파일한 다음, 사용자 정의 도구(src/tools/coverage-dump)를 사용하여 IR에 포함된 커버리지 매핑을 추출하고 보기 좋게 출력합니다. 이 테스트들은 프로파일러 런타임이 필요하지 않으므로 PR CI 작업에서 실행되며, 로컬에서도 쉽게 실행하거나 bless할 수 있습니다.

이 커버리지 맵 테스트들은 MIR 로워링이나 MIR 최적화의 변경에 민감할 수 있어서, 매핑은 다르지만 동일한 커버리지 보고서를 만들어 내는 결과가 나올 수 있습니다.

경험적으로, 커버리지 관련 코드를 변경하지 않는 PR은 coverage-run 테스트가 여전히 통과하는 한 실제 변경 내용을 걱정하지 말고 필요에 따라 coverage-map 테스트를 자유롭게 다시 bless해도 됩니다.

coverage-run 스위트

coverage-run 모드에서 이 테스트들은 커버리지 보고에 대한 종단 간 테스트를 수행합니다. 이 테스트들은 커버리지 계측을 적용하여 테스트 프로그램을 컴파일하고, 그 프로그램을 실행하여 원시 커버리지 데이터를 생성한 다음, LLVM 도구를 사용하여 그 데이터를 사람이 읽을 수 있는 코드 커버리지 보고서로 가공합니다.

계측된 바이너리는 LLVM 프로파일러 런타임과 링크되어야 하므로, bootstrap.toml에서 프로파일러 런타임이 활성화되어 있지 않으면 coverage-run 테스트는 자동으로 건너뜁니다:

build.profiler = true

이는 또한 이 테스트들이 보통 PR CI 작업에서는 실행되지 않는다는 뜻이기도 하지만, 병합에 사용되는 전체 CI 작업의 일부로는 실행됩니다.

coverage-run-rustdoc 스위트

tests/coverage-run-rustdoc의 테스트들은 계측된 doctest도 실행하여 커버리지 보고서에 포함합니다. 이렇게 하면 주 coverage 스위트만 실행할 때 rustdoc을 빌드하지 않아도 됩니다.

크래시 테스트

tests/crashes는 컴파일러에서 ICE나 패닉을 일으키거나 다른 방식으로 크래시를 일으킬 것으로 예상되는 테스트 모음으로, 의도치 않은 수정을 추적하는 데 쓰입니다. 이전에는 이 작업을 https://github.com/rust-lang/glacier에서 했지만, rust-lang/rust 테스트 스위트 안에서 하는 편이 더 편리합니다.

이 스위트의 테스트는 반드시 rustc에서 ICE나 패닉을 일으키거나 다른 방식으로 크래시를 일으켜야 합니다. rustc가 1이나 0이 아닌 종료 상태로 종료되면 테스트는 “통과“합니다.

자세한 stdout/stderr를 보려면 다음과 같이 COMPILETEST_VERBOSE_CRASHES=1을 설정해야 합니다.

$ COMPILETEST_VERBOSE_CRASHES=1 ./x test tests/crashes/999999.rs --stage 1

누구나 이슈 트래커에서 “추적되지 않는” 크래시를 추가할 수 있습니다. 여러 이슈의 테스트 케이스를 하나의 PR에 함께 포함하는 것을 강력히 권장합니다. 이때 각 이슈 번호는 파일 이름(12345.rs면 충분합니다)에 기재하고, 파일 안에도 //@ known-bug: #12345 지시문으로 기재해야 합니다. PR이 병합되면 관련 이슈에 S-bug-has-test 레이블을 붙여 주십시오.

크래시 중 하나를 우연히 수정했다면 해당 테스트를 tests/ui의 적절한 하위 디렉터리로 옮기고 의미 있는 이름을 붙여 주십시오. 파일 맨 위에 이 테스트가 존재하는 이유를 설명하는 문서 주석을 추가해 주십시오. 예제가 이전에 어떻게 rustc를 크래시시켰는지, 그리고 이를 수정하기 위해 무엇을 했는지 간단히 설명할 수 있다면 더욱 좋습니다.

다음을

Fixes #NNNNN
Fixes #MMMMM

풀 리퀘스트 설명에 추가하면 병합 시 해당 티켓이 자동으로 닫힙니다.

먼저 수정 사항이 이슈의 일부만이 아니라 근본 원인을 실제로 해결하는지 확인하십시오. 이슈 번호는 파일 이름이나 테스트 파일 안의 //@ known-bug 지시문에서 찾을 수 있습니다.

보조 크레이트 빌드하기

일부 테스트에서는 추가 보조 크레이트를 컴파일해야 하는 경우가 흔합니다. 이를 돕는 여러 지시문이 있습니다:

  • aux-build
  • aux-crate
  • aux-bin
  • aux-codegen-backend
  • proc-macro

aux-build는 지정한 소스 파일로부터 별도의 크레이트를 빌드합니다. 소스 파일은 테스트 파일 옆의 auxiliary라는 디렉터리에 있어야 합니다.

//@ aux-build: my-helper.rs

extern crate my_helper;
// ... You can use my_helper.

보조 크레이트는 가능하면 dylib로 빌드됩니다(dylib를 지원하지 않는 플랫폼이거나 보조 파일에 no-prefer-dynamic 헤더가 지정된 경우는 제외합니다). 외부 크레이트를 찾는 데에는 -L 플래그가 사용됩니다.

aux-crate는 aux-build와 매우 비슷합니다. 하지만 --extern 플래그를 사용해 외부 크레이트에 링크하여, 해당 크레이트를 extern prelude로 사용할 수 있게 합니다. 이를 통해 의존성 이름 변경과 같은 --extern 플래그의 추가 구문을 지정할 수 있습니다. 예를 들어, //@ aux-crate: foo=bar.rs는 auxiliary/bar.rs를 컴파일하고 테스트 안에서 foo라는 이름으로 사용할 수 있게 합니다. 이는 Cargo가 의존성 이름을 바꾸는 방식과 비슷합니다. --extern 수정자를 지정하는 것도 가능합니다. 예를 들어, //@ aux-crate: noprelude:foo=bar.rs와 같이 지정합니다.

aux-bin은 aux-build와 비슷하지만 라이브러리 대신 바이너리를 빌드합니다. 바이너리는 테스트의 작업 디렉터리를 기준으로 auxiliary/bin에 위치하게 됩니다.

aux-codegen-backend는 aux-build와 비슷하지만, 메인 파일을 빌드할 때 컴파일된 dylib를 -Zcodegen-backend에 전달합니다. 이는 컴파일러 크레이트를 사용해야 하므로 tests/ui-fulldeps에 있는 테스트에서만 동작합니다.

보조 proc-macro

proc-macro 의존성이 필요하다면 proc-macro 지시문을 사용할 수 있습니다. 이 지시문은 aux-build와 똑같이 동작합니다. 즉, proc-macro 테스트 보조 파일을 메인 테스트 파일과 같은 상위 폴더 아래의 auxiliary 폴더에 두어야 합니다. 다만 proc-macro 테스트 보조 파일에는 aux-build와 비교해 네 가지 사전 설정 동작이 추가로 적용됩니다:

  1. 보조 테스트 파일은 --crate-type proc-macro로 빌드됩니다.
  2. 보조 테스트 파일은 -C prefer-dynamic 없이 빌드됩니다. 즉, 보조 크레이트에 대해 dylib를 생성하려고 시도하지 않습니다.
  3. 보조 크레이트는 --extern <aux_crate_name>을 통해 extern prelude로 테스트 파일에서 사용할 수 있게 됩니다. UI 테스트는 기본적으로 2015 에디션을 사용하므로, use 임포트에서 보조 크레이트 이름을 사용하려면 메인 테스트 파일이 2018 이상의 에디션을 사용하지 않는 한 여전히 extern <aux_crate_name>을 지정해야 한다는 점에 유의하십시오.
  4. proc_macro 크레이트는 extern prelude 모듈로 제공됩니다. extern proc_macro;에도 2015 에디션과 그 이후 에디션 사이의 구분이 동일하게 적용됩니다.

예를 들어 테스트 tests/ui/cat/meow.rs와 proc-macro 보조 파일 tests/ui/cat/auxiliary/whiskers.rs가 있다고 합시다:

tests/ui/cat/
    meow.rs                 # main test file
    auxiliary/whiskers.rs   # auxiliary
// tests/ui/cat/meow.rs

//@ proc-macro: whiskers.rs

extern crate whiskers; // needed as ui test defaults to edition 2015

fn main() {
  whiskers::identity!();
}
// tests/ui/cat/auxiliary/whiskers.rs

extern crate proc_macro;
use proc_macro::*;

#[proc_macro]
pub fn identity(ts: TokenStream) -> TokenStream {
    ts
}

참고: proc-macro 헤더는 현재 rustdoc 테스트에서 build-aux-doc 헤더와 함께 동작하지 않습니다. 이 경우 aux-build 헤더를 사용하고, proc-macro 안에서 #![crate_type="proc_macro"]와 //@ force-host 및 //@ no-prefer-dynamic 헤더를 사용해야 합니다.

리비전

리비전을 사용하면 하나의 테스트 파일을 여러 테스트에 사용할 수 있습니다. 이를 위해 파일 맨 위에 특별한 지시문을 추가합니다:

//@ revisions: foo bar baz

이렇게 하면 테스트가 세 번 컴파일(및 테스트)되며, 한 번은 --cfg foo로, 한 번은 --cfg bar로, 한 번은 --cfg baz로 수행됩니다. 따라서 테스트 안에서 #[cfg(foo)] 등을 사용하여 각 결과를 조정할 수 있습니다.

지시문과 예상 오류 메시지를 특정 리비전에 맞게 지정할 수도 있습니다. 이렇게 하려면 다음과 같이 지시문의 경우 //@ 뒤에, UI 오류 주석의 경우 // 뒤에 [revision-name]을 추가하십시오:

// A flag to pass in only for cfg `foo`:
//@[foo]compile-flags: -Z verbose-internals

#[cfg(foo)]
fn test_foo() {
    let x: usize = 32_u32; //[foo]~ ERROR mismatched types
}

//[foo,bar,baz]~^처럼 쉼표로 구분된 목록으로 여러 리비전을 지정할 수 있습니다.

LLVM FileCheck 도구를 사용하는 테스트 스위트에서는 현재 리비전 이름이 FileCheck 지시문의 추가 접두사로도 등록됩니다:

//@ revisions: NORMAL COVERAGE
//@[COVERAGE] compile-flags: -Cinstrument-coverage
//@[COVERAGE] needs-profiler-runtime

// COVERAGE:   @__llvm_coverage_mapping
// NORMAL-NOT: @__llvm_coverage_mapping

// CHECK: main
fn main() {}

모든 지시문이 특정 리비전에 맞게 지정되었을 때 의미를 갖는 것은 아니라는 점에 유의하십시오. 예를 들어 ignore-test 지시문(및 모든 “ignore” 지시문)은 현재 특정 리비전이 아니라 테스트 전체에만 적용됩니다. 리비전에 맞게 지정했을 때 실제로 동작하도록 의도된 지시문은 오류 패턴과 컴파일러 플래그뿐입니다.

다음 테스트 스위트는 리비전을 지원하지 않는다는 점에 유의하십시오:

  • codegen-units
  • run-make
  • rustdoc-html
  • rustdoc-json

사용하지 않는 리비전 이름 무시하기

일반적으로 다른 지시문과 오류 주석에서 언급된 리비전 이름은 revisions 지시문에 선언된 실제 리비전과 일치해야 합니다. 이는 ./x test tidy 검사로 강제됩니다.

어떤 이유로 리비전 목록에서 리비전 이름을 일시적으로 제거해야 한다면, 대신 해당 리비전 이름을 //@ unused-revision-names: 헤더에 추가하여 위 검사를 억제할 수 있습니다.

사용하지 않는 이름으로 *를 지정하면(즉, //@ unused-revision-names: *) 사용하지 않는 어떤 리비전 이름이든 언급할 수 있습니다.

비교 모드

Compiletest는 비교 모드라고 하는 여러 모드로 실행할 수 있으며, 이를 사용하여 서로 다른 컴파일러 플래그를 활성화했을 때 모든 테스트의 동작을 비교할 수 있습니다. 이를 통해 특정 플래그에서 어떤 차이가 나타날 수 있는지 드러내고, 발생할 수 있는 문제를 확인할 수 있습니다.

다른 모드로 테스트를 실행하려면 --compare-mode CLI 플래그를 전달해야 합니다:

./x test tests/ui --compare-mode=next-solver

사용할 수 있는 비교 모드는 다음과 같습니다:

  • polonius — -Zpolonius=next를 사용하여 Polonius로 실행합니다.
  • next-solver — -Znext-solver를 사용하여 차세대 트레이트 솔버로 실행합니다.
  • next-solver-coherence — -Znext-solver=coherence를 사용하여 차세대 트레이트 솔버로 일관성(coherence) 검사를 실행합니다.
  • split-dwarf — -Csplit-debuginfo=unpacked를 사용하여 패킹되지 않은 split-DWARF로 실행합니다.
  • split-dwarf-single — -Csplit-debuginfo=packed를 사용하여 패킹된 split-DWARF로 실행합니다.

UI 테스트가 모드마다 다른 출력을 지원하는 방식에 대한 자세한 내용은 UI 비교 모드를 참조하십시오.

CI에서 비교 모드는 하나의 Linux 빌더에서만, 그리고 다음 설정으로만 사용됩니다.

  • tests/debuginfo: split-dwarf 모드를 사용합니다. 이를 통해 split-DWARF를 활성화했을 때 어떤 debuginfo 테스트도 영향을 받지 않도록 보장할 수 있습니다.

비교 모드는 리비전과 별개라는 점에 유의하십시오. ./x test tests/ui를 실행하면 모든 리비전이 테스트되지만, 비교 모드는 --compare-mode 플래그를 통해 각각 수동으로 실행해야 합니다.

병렬 프런트엔드

Compiletest를 --parallel-frontend-threads 플래그와 함께 실행하면 컴파일러를 병렬 모드로 실행할 수 있습니다. 이를 사용하여 컴파일러가 병렬 모드에서도 비병렬 모드와 동일한 출력을 생성하는지 확인하고, 병렬 모드에서 발생할 수 있는 문제를 점검할 수 있습니다.

병렬 모드로 테스트를 실행하려면 --parallel-frontend-threads CLI 플래그를 전달해야 합니다.

./x test tests/ui -- --parallel-frontend-threads=N --iteration-count=M

여기서 N은 병렬 프런트엔드에 사용할 스레드 수이고, M은 (비결정성을 포착할 가능성을 높이기 위해) 병렬 모드에서 각 테스트를 실행할 횟수입니다.

또한 --parallel-frontend-threads로 실행할 때는 병렬 프런트엔드의 출력이 줄 순서 측면에서 비결정적일 수 있으므로, 모든 테스트에 compare-output-by-lines 지시문이 암묵적으로 적용됩니다.

병렬 프런트엔드는 현재 UI 테스트에서만 사용할 수 있으며, 다른 테스트 스위트에서는 아직 지원되지 않습니다.