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

UI 테스트

UI 테스트는 compiletest의 특정 테스트 스위트입니다.

소개

tests/ui에 있는 테스트는 범용 테스트 모음으로, 주로 컴파일러의 콘솔 출력을 검증하는 데 초점을 맞추지만 그 밖의 다양한 용도로도 사용할 수 있습니다. 예를 들어, 테스트가 결과 프로그램을 실행하여 그 동작을 검증하도록 구성할 수도 있습니다.

tests/ui 아래 각 하위 디렉터리의 용도에 대한 개요는 README.md를 참조하십시오. 이는 새 테스트를 작성하면서 테스트를 배치할 범주를 찾고 있을 때 유용합니다.

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

테스트의 일반적인 구조

테스트는 tests/ui 디렉터리에 위치한 Rust 소스 파일로 구성됩니다. 테스트는 목적과 테스트 범주에 따라 적절한 하위 디렉터리에 배치해야 합니다. 테스트를 tests/ui에 직접 배치하는 것은 허용되지 않습니다.

compiletest는 rustc를 사용하여 테스트를 컴파일하고, 그 출력을 테스트 옆에 위치한 .stdout 또는 .stderr 파일에 저장된 예상 출력과 비교합니다. 자세한 내용은 출력 비교를 참조하십시오.

또한 오류와 경고는 소스 파일 안에 주석으로 표시해야 합니다. 자세한 내용은 오류 주석을 참조하십시오.

//@ 접두사가 붙은 특수 주석 형태의 compiletest 지시문은 테스트를 컴파일하는 방식과 예상되는 동작을 제어합니다.

대부분의 테스트가 컴파일러 오류를 테스트하므로, 테스트는 컴파일에 실패할 것으로 예상됩니다. 지시문으로 이 동작을 변경할 수 있습니다. 통과/실패 예상 제어하기를 참조하십시오.

기본적으로 테스트는 실행 가능한 바이너리로 빌드됩니다. 다른 크레이트 타입이 필요하다면 #![crate_type] 속성을 사용하여 필요에 맞게 설정할 수 있습니다.

출력 비교

UI 테스트는 컴파일러의 예상 출력을 테스트 옆의 .stderr 및 .stdout 스냅숏에 저장합니다. 일반적으로 이 파일들은 --bless CLI 옵션으로 생성한 다음, 직접 살펴보며 예상한 내용이 들어 있는지 확인합니다.

출력은 원치 않는 차이를 무시하도록 정규화됩니다. 정규화 절을 참조하십시오. 파일이 없으면 compiletest는 해당 출력이 비어 있을 것으로 예상합니다.

정규화, 리비전 및 이어지는 다른 도구 대부분을 사용하는 흔한 이유는 플랫폼 간 차이를 고려하기 위해서입니다. 이러한 도구 대신 다른 방법을 고려하십시오. 예를 들어, 크로스 컴파일을 사용하도록 테스트를 고쳐 무효일 가능성이 있는 모든 ABI를 테스트하는 대신, 모든 플랫폼에서 무효인 extern "rust-invalid" ABI를 사용하는 방법이 있습니다.

stdout/stderr 파일은 여러 개일 수 있습니다. 일반적인 형식은 다음과 같습니다.

*test-name*`.`*revision*`.`*compare_mode*`.`*extension*
  • test-name에는 점이 포함될 수 없습니다. 테스트 이름에는 점이 들어갈 수 없습니다. 이는 테스트 출력 파일 이름의 일반 형식을 예측 가능하게 만들어, 떠도는 테스트 출력 파일을 추적하기 위해 패턴 매칭을 할 수 있도록 하기 위함입니다.
  • revision은 리비전 이름입니다. 리비전을 사용하지 않을 때는 포함되지 않습니다.
  • compare_mode는 비교 모드입니다. 이 파일은 해당 비교 모드가 활성화된 경우에만 검사됩니다. 파일이 존재하지 않으면 compiletest는 비교 모드가 없는 파일을 검사합니다.
  • extension은 검사 대상 출력의 종류입니다.
    • stderr — 컴파일러 stderr
    • stdout — 컴파일러 stdout
    • run.stderr — 테스트 실행 시의 stderr
    • run.stdout — 테스트 실행 시의 stdout
    • 64bit.stderr — 64비트 타깃에서 stderr-per-bitwidth 지시문을 사용할 때의 컴파일러 stderr
    • 32bit.stderr — 32비트 타깃에서 stderr-per-bitwidth 지시문을 사용할 때의 컴파일러 stderr

간단한 예로는 foo.rs 테스트 옆에 있는 foo.stderr가 있습니다. 더 복잡한 예로는 foo.my-revision.polonius.stderr가 있습니다.

compiletest가 출력 파일을 검사하는 방식을 바꾸는 지시문이 몇 가지 있습니다.

  • stderr-per-bitwidth — 타깃 포인터 너비에 따라 별도의 출력 파일을 검사합니다. 대신 normalize-stderr 지시문을 사용하는 것을 고려하십시오(정규화 참조).
  • dont-check-compiler-stderr — 컴파일러의 stderr를 무시합니다.
  • dont-check-compiler-stdout — 컴파일러의 stdout을 무시합니다.
  • compare-output-by-lines — 일부 테스트는 출력 순서가 비결정적이므로 줄 단위로 비교해야 합니다.

UI 테스트는 -Zdeduplicate-diagnostics=no 플래그와 함께 실행되며, 이 플래그는 rustc에 내장된 진단 중복 제거 메커니즘을 비활성화합니다. 따라서 출력에서 일부 중복된 메시지를 볼 수 있습니다. 이를 통해 중복된 진단이 생성되는 상황을 파악하기 쉬워집니다.

정규화

컴파일러 출력은 플랫폼 간의 출력 차이, 주로 파일 이름과 관련된 차이를 없애기 위해 정규화됩니다.

compiletest는 컴파일러 출력에 대해 다음과 같은 치환을 수행합니다.

  • 테스트가 정의된 디렉터리는 $DIR로 치환됩니다. 예: /path/to/rust/tests/ui/error-codes
  • 표준 라이브러리 소스 디렉터리는 $SRC_DIR로 치환됩니다. 예: /path/to/rust/library
  • $SRC_DIR 안의 경로에 대한 줄 번호와 열 번호는 LL:COL로 치환됩니다. 이렇게 하면 표준 라이브러리의 구조가 바뀌더라도 .stderr 파일이 광범위하게 변경되지 않도록 하는 데 도움이 됩니다. 예: $SRC_DIR/alloc/src/sync.rs:53:46
  • 테스트 출력이 저장되는 기본 디렉터리는 $TEST_BUILD_DIR로 대체됩니다. 이는 몇몇 드문 경우에만 나타납니다. 예시: /path/to/rust/build/x86_64-unknown-linux-gnu/test/ui
  • 표준 라이브러리 소스의 실제 디렉터리는 $SRC_DIR_REAL로 대체됩니다.
  • 컴파일러 소스의 실제 디렉터리는 $COMPILER_DIR_REAL로 대체됩니다.
  • 탭은 \t로 대체됩니다.
  • 경로 안의 역슬래시(\)는 (휴리스틱을 사용하여) 슬래시(/)로 변환됩니다. 이는 Windows 스타일 경로와의 차이를 정규화하는 데 도움이 됩니다.
  • CRLF 줄바꿈은 LF로 변환됩니다.
  • //~ ERROR some message와 같은 오류 줄 주석은 제거됩니다.
  • 다양한 v0 및 레거시 심볼 해시는 [HASH]나 <SYMBOL_HASH>와 같은 자리 표시자로 대체됩니다.

또한 컴파일러는 -Z ui-testing 플래그와 함께 실행되며, 이 플래그를 사용하면 컴파일러가 직접 진단 출력을 일부 변경하여 UI 테스트에 더 적합하게 만듭니다.

예를 들어, 출력에서 줄 번호를 익명화합니다(각 소스 줄 앞에 붙는 줄 번호가 LL로 대체됩니다). 극히 드문 경우에는 //@ compile-flags: -Z ui-testing=no 지시문으로 이 모드를 비활성화할 수 있습니다.

-Z ui-testing=no를 사용할 때는 UI 테스트 스위트를 실행하는 터미널의 너비에 따라 테스트가 실패하거나 통과하는 일이 없도록 --diagnostic-width 인자도 함께 설정해야 합니다.

참고: 테스트를 가리키는 --> 줄의 줄 번호와 열 번호는 정규화되지 않으며 있는 그대로 남습니다. 이렇게 하면 컴파일러가 계속 올바른 위치를 가리키는지 확인할 수 있고, stderr 파일도 읽기 쉽게 유지됩니다. 이상적으로는 모든 줄/열 정보를 유지하는 것이 좋겠지만, 그렇게 하면 소스를 조금만 변경해도 diff가 커지고 병합 충돌과 테스트 오류가 더 자주 발생합니다.

때로는 이러한 내장 정규화만으로는 충분하지 않습니다. 이런 경우에는 normalize-* 지시문을 사용하여 사용자 정의 정규화 규칙을 제공할 수 있습니다. 예를 들면 다음과 같습니다.

//@ normalize-stdout: "foo" -> "bar"
//@ normalize-stderr: "foo" -> "bar"
//@ normalize-stderr-32bit: "fn\(\) \(32 bits\)" -> "fn\(\) \($$PTR bits\)"
//@ normalize-stderr-64bit: "fn\(\) \(64 bits\)" -> "fn\(\) \($$PTR bits\)"

이는 32비트 플랫폼에서 컴파일러가 stderr에 fn() (32 bits)를 쓸 때마다 이를 fn() ($PTR bits)로 정규화해야 한다고 테스트에 알려 줍니다. 64비트의 경우도 마찬가지입니다. 대체는 regex 크레이트가 제공하는 기본 정규식 문법을 사용하는 정규식으로 수행됩니다.

해당 참조 파일은 정규화된 출력을 사용하여 32비트와 64비트 플랫폼을 모두 테스트합니다.

...
   |
   = note: source type: fn() ($PTR bits)
   = note: target type: u16 (16 bits)
...

구체적인 사용 예시는 ui/transmute/main.rs와 main.stderr를 참고하십시오.

오류 주석

오류 주석은 컴파일러가 내보낼 것으로 예상되는 오류를 지정합니다. 오류 주석은 소스에서 오류가 위치한 줄에 “붙어” 있습니다.

fn main() {
    boom  //~ ERROR cannot find value `boom` in this scope [E0425]
}

UI 테스트에는 컴파일러 출력 전체를 담은 .stderr 파일이 있지만, UI 테스트에서는 오류를 소스 안에도 주석으로 표시해야 합니다. .stderr 파일은 보통 자동으로 생성되므로, 이렇게 중복해 두면 실수를 방지하는 데 도움이 됩니다. 또한 .stderr 파일을 소스와 비교할 필요 없이 파일 하나만 보고도 오류 범위가 어디를 가리켜야 하는지 바로 확인할 수 있습니다. 마지막으로, 오류 주석은 예상하지 못한 추가 오류가 발생하지 않도록 보장합니다.

이러한 주석은 여러 형태가 있지만, 일반적으로 진단 수준(예: ERROR)과 예상되는 오류 출력의 부분 문자열로 이루어진 주석입니다. 메시지 전체를 적을 필요는 없지만, 주석만 보고도 의미를 알 수 있도록 메시지의 중요한 부분은 반드시 포함하십시오.

대부분의 오류 주석은 진단이 발생한 줄과 일치해야 합니다. 메시지를 줄과 대응시키는 방법은 여러 가지가 있습니다(아래 예시를 참고하십시오).

  • ~: 오류 수준과 메시지를 현재 줄에 대응시킵니다.
  • ~^: 오류 수준과 메시지를 오류 주석 줄의 이전 줄에 대응시킵니다. 캐럿(^)을 하나 추가할 때마다 한 줄씩 더 위를 가리키므로, ~^^^는 오류 주석 줄에서 세 줄 위를 가리킵니다.
  • ~|: 오류 수준과 메시지를 이전 주석과 같은 줄에 대응시킵니다. 같은 줄에 여러 메시지가 대응될 때는 캐럿을 여러 개 쓰는 것보다 이 방법이 더 편리합니다.
  • ~v: 오류 수준과 메시지를 오류 주석 줄의 다음 줄에 대응시킵니다. 기호(v)를 하나 추가할 때마다 한 줄씩 더 아래를 가리키므로, ~vvv는 오류 주석 줄에서 세 줄 아래를 가리킵니다.

예시:

let _ = same_line; //~ ERROR undeclared variable
fn meow(_: [u8]) {}
//~^ ERROR unsized
//~| ERROR anonymous parameters

//~(또는 다른 변형)와 그 뒤에 오는 텍스트 사이의 공백 문자는 무시해도 됩니다(즉, //~ ERROR와 //~ERROR 사이에는 의미상 차이가 없지만, 코드베이스에서는 전자가 더 흔하게 쓰입니다).

~? <diagnostic kind>(예: ~? ERROR)는 줄 정보가 아예 없는 진단이나, 줄 정보가 주 테스트 파일1 밖에 있는 진단을 매칭하는 데 사용합니다. 이러한 주석은 테스트 파일의 어느 줄에나 둘 수 있습니다.

오류 주석 예시

다음은 UI 테스트 소스의 여러 줄에 오류 주석을 다는 예시입니다.

오류 줄에 위치하는 경우

//~ ERROR 관용구를 사용하십시오.

fn main() {
    let x = (1, 2, 3);
    match x {
        (_a, _x @ ..) => {} //~ ERROR `_x @` is not allowed in a tuple
        _ => {}
    }
}

오류 줄 아래에 위치하는 경우

//~^ 관용구를 사용하되, 문자열에 넣는 캐럿의 개수로 몇 줄 위인지를 나타내십시오. 아래 예시에서는 오류 줄이 오류 주석 줄보다 네 줄 위에 있으므로 주석에 캐럿이 네 개 들어 있습니다.

fn main() {
    let x = (1, 2, 3);
    match x {
        (_a, _x @ ..) => {}  // <- the error is on this line
        _ => {}
    }
}
//~^^^^ ERROR `_x @` is not allowed in a tuple

위쪽 오류 주석 줄에서 정한 것과 같은 오류 줄을 사용하는 경우

//~| 관용구를 사용하여 위쪽 오류 주석 줄과 같은 오류 줄을 지정하십시오.

struct Binder(i32, i32, i32);

fn main() {
    let x = Binder(1, 2, 3);
    match x {
        Binder(_a, _x @ ..) => {}  // <- the error is on this line
        _ => {}
    }
}
//~^^^^ ERROR `_x @` is not allowed in a tuple struct
//~| ERROR this pattern has 1 field, but the corresponding tuple struct has 3 fields [E0023]

오류 줄 위에 위치하는 경우

//~v 관용구를 사용하되, 문자열에 넣는 v의 개수로 몇 줄 아래인지를 나타내십시오. 이 방법은 주로 파일 끝에서 발생하는 닫히지 않은 구분자나 닫히지 않은 리터럴 같은 오류를 매칭하는 렉서 또는 파서 테스트에서 사용합니다.

// ignore-tidy-trailing-newlines
//~v ERROR this file contains an unclosed delimiter
fn main((ؼ

줄 정보가 없는 오류

줄 정보가 없는 오류를 매칭하려면 //~?를 사용하십시오. //~?는 정확하게 동작하며, 줄 정보가 있는 오류와는 매칭되지 않습니다. //@ error-pattern은 정확하지 않고 모든 오류를 확인하지도 않으므로, 컴파일러 진단을 매칭하려는 테스트에서는 //@ error-pattern보다 이 방법을 우선 사용하십시오.

//@ compile-flags: --print yyyy

//~? ERROR unknown print request: `yyyy`

error-pattern

error-pattern 지시문은 특정 스팬이 없는 런타임 메시지에 사용할 수 있으며, 예외적인 경우에는 컴파일 타임 메시지에도 사용할 수 있습니다.

다음 테스트를 생각해 봅시다.

fn main() {
    let a: *const [_] = &[1, 2, 3];
    unsafe {
        let _b = (*a)[3];
    }
}

이 테스트가 “index out of bounds“를 출력하는지 확인하려고 하지만, 런타임 오류에는 스팬이 없으므로 ERROR 애너테이션을 사용할 수 없습니다. 이럴 때 error-pattern 지시문을 사용합니다.

//@ error-pattern: index out of bounds
fn main() {
    let a: *const [_] = &[1, 2, 3];
    unsafe {
        let _b = (*a)[3];
    }
}

컴파일 타임 출력을 엄격하게 테스트하려면 스팬이 없는 진단을 위한 //~? 애너테이션을 포함하여 줄 애너테이션 //~를 최대한 사용하십시오.

컴파일 타임 출력이 타깃에 따라 달라지거나 너무 장황하다면, //@ dont-require-annotations: <diagnostic-kind> 지시문을 사용하여 줄 애너테이션 검사가 모든 진단을 요구하지 않도록 하십시오. 이 모드에서는 일부 컴파일러 메시지가 애너테이션으로 다루어지지 않은 채로 남을 수 있습니다.

런타임 출력을 검사할 때는 //@ check-run-results가 더 나을 수 있습니다.

런타임 패닉 출력에서 특정 문자열 패턴을 찾는 경우처럼 위의 방법이 모두 통하지 않을 때에만 error-pattern을 사용하십시오.

줄 애너테이션 //~와 error-pattern은 서로 호환되며 같은 테스트에서 함께 사용할 수 있습니다.

진단 종류(오류 수준)

사용할 수 있는 진단 종류는 다음과 같습니다.

  • ERROR
  • WARN(또는 WARNING)
  • NOTE
  • HELP
  • SUGGESTION
  • RAW

SUGGESTION 종류는 진단 제안에서 기대하는 대체 텍스트가 무엇인지 지정하는 데 사용합니다. RAW 종류는 컴파일러가 구조화된 json 대신 또는 그에 더해 때때로 내보내는 비구조화 출력의 줄을 매칭하는 데 사용할 수 있습니다.

기본적으로 ERROR와 WARN 종류는 줄 애너테이션 //~로 빠짐없이 다루어야 합니다.

그 밖의 종류는 테스트 파일에 해당 종류의 애너테이션이 하나 이상 있을 때에만 줄 애너테이션을 달아야 합니다. 예를 들어 //~ NOTE를 하나 작성하면 파일 안의 다른 모든 //~ NOTE도 명시적으로 작성해야 합니다.

빠짐없는 애너테이션 요구를 해제하려면 //@ dont-require-annotations 지시문을 사용하십시오. 예를 들어 노트에 선택적으로 애너테이션을 달려면 //@ dont-require-annotations: NOTE를 사용하십시오. 타깃에 따라 달라지는 컴파일러 출력처럼 중대한 이유가 없는 한 ERROR와 WARN에는 이 지시문을 사용하지 마십시오.

일부 진단은 종류나 지시문과 관계없이 줄 애너테이션을 달 필요가 전혀 없습니다. 여러 줄 진단의 두 번째 이후 줄이나, aborting due to N previous errors처럼 어디에나 나타나는 진단이 그 예입니다.

미사용 경고는 대개 테스트의 초점이 아니므로, UI 테스트는 기본적으로 -A unused 플래그를 사용하여 모든 미사용 경고를 무시합니다. 하지만 간단한 코드 예제에서는 미사용 경고가 자주 발생합니다. 테스트가 특별히 미사용 경고를 테스트하는 것이라면 필요에 따라 적절한 #![warn(unused)] 속성을 추가하기만 하면 됩니다.

cfg 리비전

리비전을 사용할 때는 현재 리비전에 따라 서로 다른 메시지를 조건부로 검사할 수 있습니다. 다음과 같이 리비전 cfg 이름을 대괄호 안에 넣으면 됩니다:

//@ edition:2018
//@ revisions: mir thir
//@[thir] compile-flags: -Z thir-unsafeck

async unsafe fn f() {}

async fn g() {
    f(); //~ ERROR call to unsafe function is unsafe
}

fn main() {
    f(); //[mir]~ ERROR call to unsafe function is unsafe
}

이 예제에서 두 번째 오류 메시지는 mir 리비전에서만 출력됩니다. thir 리비전은 첫 번째 오류만 출력합니다.

cfg로 인해 컴파일러가 서로 다른 출력을 내보내는 경우, 테스트는 각 출력에 대해 여러 개의 .stderr 파일을 가질 수 있습니다. 위 예제에서는 각 리비전의 서로 다른 출력을 담은 .mir.stderr 파일과 .thir.stderr 파일이 존재하게 됩니다.

참고: cfg 리비전은 #[cfg] 속성을 사용하여 소스 코드 내부에서도 동작합니다.

관례상 항상 거짓인 설정이 필요할 때는 FALSE cfg를 사용합니다.

통과/실패 기대 결과 제어하기

대부분의 테스트는 잘못된 입력과 오류 진단을 검사하기 때문에, 기본적으로 UI 테스트는 컴파일 오류를 발생시킬 것으로 기대됩니다. 하지만 컴파일이 성공할 것으로 기대되는 UI 테스트를 만들 수도 있으며, 그 결과로 생성된 프로그램을 실행할 수도 있습니다. 다음 지시문 중 하나를 추가하기만 하면 됩니다:

  • 통과 지시문:
    • //@ check-pass — 컴파일은 성공해야 하지만 코드 생성은 건너뜁니다(코드 생성은 비용이 크고 대부분의 경우 실패하지 않아야 합니다).
    • //@ build-pass — 컴파일과 링크는 성공해야 하지만 결과 바이너리는 실행하지 않습니다.
    • //@ run-pass — 컴파일이 성공해야 하며, 결과 바이너리를 실행하면 성공을 나타내는 종료 코드 0으로 종료되어야 합니다.
  • 실패 지시문:
    • //@ check-fail — 컴파일이 실패해야 합니다(코드 생성 단계는 건너뜁니다). 이것이 UI 테스트의 기본값입니다.
    • //@ build-fail — 코드 생성 단계에서 컴파일이 실패해야 합니다. 이 경우 rustc를 두 번 실행합니다:
      • 첫 번째 실행은 코드 생성 단계 없이 컴파일이 성공하는지 확인하기 위한 것입니다
      • 두 번째 실행은 전체 컴파일이 실패하는지 확인하기 위한 것입니다
    • //@ run-fail — 컴파일은 성공해야 하지만, 결과 바이너리를 실행하면 일반적인 실패를 나타내는 1..=127 범위의 코드로 종료되어야 합니다. 언와인드를 지원하지 않는 타깃에서는 크래시도 허용됩니다.
    • //@ run-crash — 컴파일은 성공해야 하지만, 결과 바이너리를 실행하면 크래시로 실패해야 합니다. 크래시는 “0..=127 범위의 코드로 종료되지 않는 것“으로 정의됩니다.
      • Linux에서의 예: SIGABRT 또는 SIGSEGV에 의한 종료입니다.
      • Windows에서의 예: STATUS_ILLEGAL_INSTRUCTION(0xC000001D)에 해당하는 코드로 종료되는 것입니다.
    • //@ run-fail-or-crash — 컴파일은 성공해야 하지만, 결과 바이너리를 실행하면 run-fail 또는 run-crash 중 하나가 되어야 합니다. 테스트가 일부 타깃에서는 크래시하지만 다른 타깃에서는 단순히 실패하는 경우에 유용합니다.

run-pass, run-fail, run-crash, run-fail-or-crash 테스트에서는 기본적으로 프로그램 자체의 출력을 검사하지 않습니다.

프로그램을 실행한 출력을 검사하려면 check-run-results 지시문을 포함하십시오. 이 지시어는 .run.stderr 및 .run.stdout 파일을 찾아 프로그램의 실제 출력과 비교합니다.

*-pass 지시문이 있는 테스트는 --pass 명령줄 옵션으로 재정의할 수 있습니다.

./x test tests/ui --pass check

--pass 옵션은 UI 테스트에만 영향을 줍니다. --pass check를 사용하면 UI 테스트 스위트를 훨씬 빠르게(제 시스템에서는 대략 두 배 빠르게) 실행할 수 있지만, 당연히 그만큼 검사하는 범위는 줄어듭니다.

테스트가 --pass 재정의로는 제대로 동작하지 않는 경우 no-pass-override 지시문을 사용하여 --pass CLI 플래그를 무시할 수 있습니다.

알려진 버그

known-bug 지시문은 아직 수정되지 않은 알려진 버그를 보여 주는 테스트에 사용할 수 있습니다. 알려진 버그에 대한 테스트를 추가하면 다음을 비롯한 여러 가지 이유로 도움이 됩니다:

  1. 버그가 수정되었을 때 편리하게 재사용할 수 있는, 동작하는 테스트를 유지할 수 있습니다.
  2. 버그가 우연히 수정되면 실패하는 감시 장치 역할을 합니다. 이를 통해 개발자는 관련 이슈가 수정되었으며 닫을 수도 있다는 사실을 알 수 있습니다.

이 지시문은 쉼표로 구분된 이슈 번호 또는 "unknown"을 인자로 받습니다.

  • //@ known-bug: #123, #456 (이슈가 rust-lang/rust에 있는 경우)
  • //@ known-bug: rust-lang/chalk#123456 (# 앞에 임의의 텍스트를 허용하므로, 이슈가 다른 저장소에 있을 때 유용합니다)
  • //@ known-bug: unknown (아직 알려진 이슈가 없는 경우이며, 이슈가 아직 없다면 새로 여는 것이 좋습니다)

known-bug가 있는 테스트에는 오류 주석을 포함하지 마십시오. 이러한 테스트에도 다른 일반 지시문과 stdout/stderr 파일은 포함해야 합니다.

테스트 구성

테스트 파일을 어디에 둘지 정할 때는 검사하려는 대상과 가장 잘 맞는 하위 디렉터리를 찾아보십시오. 최대한 체계적으로 정리된 상태를 유지하십시오. 물론 일부 테스트는 여러 범주에 걸쳐 있을 수 있고 기존 구조가 잘 맞지 않을 수도 있으므로 쉽지 않을 수 있습니다.

테스트가 무엇을 검사하는지 간결하게 설명하는 이름을 테스트에 붙이십시오. 테스트 이름에 이슈 번호를 넣지 마십시오. 이에 대한 더 자세한 논의는 모범 사례를 참고하십시오.

이상적으로는 여기서 어떤 코드 부분을 테스트하는지 알아보기 쉬운 디렉터리에 테스트를 추가해야 합니다(예: tests/ui/borrowck/reject-move-out-of-borrow-via-pat.rs).

새 기능을 작성할 때는 테스트를 보관할 하위 디렉터리를 만드는 것이 좋을 수 있습니다. 예를 들어 RFC 1234(“Widgets”)를 구현하고 있다면 tests/ui/rfc1234-widgets/와 같은 디렉터리에 테스트를 두는 것이 합리적일 수 있습니다.

그 밖의 경우에는 이미 적합한 디렉터리가 있을 수 있습니다.

시간이 지나면서 tests/ui 디렉터리는 매우 빠르게 커졌습니다. tidy에는 어떤 하위 디렉터리도 항목이 1000개를 넘지 않도록 보장하는 검사가 있습니다. 파일이 너무 많으면 편집기/IDE에서 다루기 불편하고 GitHub UI가 1000개를 초과하는 항목을 표시하지 않기 때문에 문제가 됩니다. 그러나 tests/ui(UI 테스트 루트 디렉터리)와 tests/ui/issues 디렉터리는 이미 항목이 1000개를 넘기 때문에 이 디렉터리들에는 다른 한도를 설정하였습니다. 따라서 새 테스트를 그곳에 넣지 말고 더 적절한 위치를 찾으십시오.

예를 들어 테스트가 클로저와 관련되어 있다면 tests/ui/closures에 넣어야 합니다. 한도에 도달하면 여기를 조정하여 한도를 늘릴 수 있습니다.

Rustfix 테스트

UI 테스트는 진단 제안이 올바르게 적용되는지, 그리고 그 결과로 변경된 코드가 올바르게 컴파일되는지 검증할 수 있습니다. 이는 run-rustfix 지시문으로 할 수 있습니다.

//@ run-rustfix
//@ check-pass
#![crate_type = "lib"]

pub struct not_camel_case {}
//~^ WARN `not_camel_case` should have an upper camel case name
//~| HELP convert the identifier to upper camel case
//~| SUGGESTION NotCamelCase

Rustfix 테스트에는 제안이 적용된 후의 소스 파일을 담은 .fixed 확장자 파일이 있어야 합니다.

  • 테스트가 실행되면 compiletest는 먼저 올바른 린트/경고가 생성되는지 확인합니다.
  • 그런 다음 제안을 적용하고 .fixed와 비교합니다(둘은 일치해야 합니다).
  • 마지막으로 수정된 소스를 컴파일하며, 이 컴파일은 반드시 성공해야 합니다.

보통 rustfix 테스트를 만들 때는 x test --bless 옵션으로 .fixed 파일을 자동으로 생성합니다.

run-rustfix 지시문을 사용하면 MachineApplicable이 아닌 제안까지 포함하여 모든 제안이 적용됩니다. 이것이 문제가 된다면 run-rustfix에 더해 rustfix-only-machine-applicable 지시문을 추가할 수 있습니다. 이 지시어는 여러 제안 수준이 섞여 있고, 기계 적용이 불가능한 제안 중 일부가 깔끔하게 적용되지 않는 경우에 사용해야 합니다.

비교 모드

비교 모드를 사용하면 평소 컴파일할 때와 다른 플래그로 모든 테스트를 실행할 수 있습니다. 경우에 따라 이로 인해 컴파일러의 출력이 달라질 수 있습니다. 이를 지원하기 위해 비교 모드에 따른 출력을 담은 별도의 출력 파일을 저장할 수 있습니다.

예를 들어 Polonius 모드를 사용할 때 테스트 foo.rs는 먼저 foo.polonius.stderr에서 예상 출력을 찾고, 찾지 못하면 일반적인 foo.stderr를 사용합니다. 모드에 따라 진단과 동작이 달라지는 경우가 있으므로 이 기능은 유용합니다. 이를 통해 어떤 테스트가 모드 간에 차이가 있는지 추적하고, 그 진단 차이를 눈으로 직접 확인할 수 있습니다.

드물게 동작이 다른 테스트를 발견한 경우, 다음과 같은 명령을 실행하여 대체 stderr 파일을 생성할 수 있습니다:

./x test tests/ui --compare-mode=polonius --bless

현재 UI 테스트에 대해서는 CI에서 어떤 비교 모드도 검사하지 않습니다.

rustc_* 테스트 속성

컴파일러는 내부 기능 rustc_attrs 뒤에 게이트된, 영구적으로 불안정한 #[rustc_*] 속성 몇 가지를 정의하며, 이 속성들은 컴파일러 내부 정보를 추가로 출력합니다. 자세한 내용은 컴파일러 디버깅의 해당 하위 절을 참고하십시오.

이러한 기능은 테스트에서 컴파일러 내부 상태를 더 정밀하고, 읽기 쉽고, 간편하게 테스트하는 데 사용할 수 있으며, 이는 “사용자에게 노출되는” Rust만으로는 같은 작업을 하기가 매우 어려운 경우에 유용합니다. 실제로 이는 “UI”(사용자 인터페이스)라는 용어를 다소 남용하는 것이며, 이러한 UI 테스트를 블랙박스 테스트에서 화이트박스 테스트로 바꾸는 것이라고 말할 수도 있습니다. 신중하게, 그리고 꼭 필요한 경우에만 사용하십시오.

UI 테스트 모드의 사전 설정 린트 수준

기본적으로 UI 테스트 모드의 테스트 스위트(tests/ui, tests/ui-fulldeps이며, tests/rustdoc-ui는 제외)는 다음을 지정합니다.

  • -A unused
  • -W unused_attributes(이 린트는 ui 테스트에서 흥미로운 경우가 많기 때문입니다)
  • -A internal_features
  • -A incomplete_features
  • -A unused_parens
  • -A unused_braces

자세한 내용은 runtest를 참고하십시오.

조건은 다음과 같습니다.

  • 해당 UI 테스트의 통과 모드는 run보다 아래 단계(즉, 체크 또는 빌드)입니다.
  • 비교 모드가 지정되지 않은 경우입니다.

이러한 린트는 ui 테스트에서 출력이 매우 많아질 수 있기 때문입니다.

필요에 따라 compile-flags 린트 수준 플래그나 소스 내 린트 수준 속성으로 이를 재정의할 수 있습니다.

rustfix 버전에는 -A unused가 전달되지 않는다는 점에 유의하십시오. 따라서 rustfix가 적용된 파일에서 unused 린트를 억제하려면 #[allow(unused)]를 사용해야 할 수도 있습니다(unused 린트 자체에 대해 rustfix를 테스트하는 경우가 있을 수 있기 때문입니다).


  1. 이는 보조(aux) 파일이나 우리가 제어할 수 없는 소스와 구별되는, ~? 주석이 들어 있는 파일을 말합니다. ↩