테스트 및 벤치마킹 (std::test)
std/test/는 단위 테스트, 벤치마킹, 그리고 경량 퍼징(fuzz) 하네스를
제공합니다 — bench.h의 실제 경과 시간(wall-clock) 측정을 제외하면 모두
프리스탠딩 환경에서 안전합니다(이 기능은 호스트 환경의 clock()이
필요하며, 프리스탠딩 모드에서는 반복 횟수만 보고되고 시간 측정은
생략됩니다).
ASSERT_* 편의 매크로는 함수형 매크로이므로, 이를 사용하는 파일은
--compat-preprocessor가 필요합니다(전처리기
참고) — 이 플래그를 피하고 싶다면 내부적으로 쓰이는 test_assert_*
함수를 직접 호출해도 됩니다.
#include <std/test/test.h>
void addition_works() {
ASSERT_EQ(2 + 2, 4);
}
int main(void) {
struct TestSuite t = std::TestSuite::new();
t.add("addition works", addition_works);
t.run();
return t.all_passed() ? 0 : 1;
}
단위 테스트 (test/test.h)
TestSuite(TestSuite::new()로 생성, 용량 TEST_MAX = 256개 케이스):
add(name, func)(func: fn void()), run()(등록된 모든
테스트를 실행하며 각 결과를 출력), print_summary()("N passed, M failed."), all_passed(). test_run_and_exit(&suite)는 실행 후
요약을 출력하고, 실패가 하나라도 있으면 exit(1)을 호출합니다 — 테스트용
main()의 마지막 줄로 쓰기 편리합니다.
단언(assertion) (테스트 함수 안에서 호출 — 첫 번째 실패는 전역
스레드 로컬 포인터를 통해 현재 실행 중인 TestCase에 기록됩니다):
| 매크로 | 함수 | 검사 내용 |
|---|---|---|
ASSERT_TRUE(e) / ASSERT_FALSE(e) |
test_assert_true |
표현식이 0이 아닙니다/0입니다 |
ASSERT_EQ(a,b) / ASSERT_NE(a,b) |
test_assert_eq_i |
long long으로서 같음/다름 |
ASSERT_STR_EQ(a,b) |
test_assert_eq_s |
NUL로 끝나는 문자열이 같음 |
ASSERT_NULL(p) / ASSERT_NOT_NULL(p) |
test_assert_null/test_assert_not_null |
?&T nullable 참조가 null입니다/아닙니다 (T에 대해 제네릭) |
test_assert_eq_u(부호 없는 long long 동등 비교)는 매크로 래퍼가
없으므로 직접 호출해야 합니다.
테스트 이름은 TEST_NAME_MAX(64)자로 잘립니다.
벤치마킹 (test/bench.h)
BenchSuite(BenchSuite::new()로 생성, 용량 BENCH_MAX = 64개
케이스): suite.add(name, func, arg, iters)는 케이스를
등록합니다 — func는 fn void(?&T)이고 arg는 그에 맞는 ?&T(또는
null)이며, 실행당 iters번 호출됩니다(add는 자기 타입 매개변수를
가진 메서드이므로
T는 func에서 오고, 맞지 않는 arg는 컴파일 오류가 됩니다). run()은 모든 케이스를 실행하고
각 BenchCase에 elapsed_s/ops_per_s를 기록하며, print_results()는
결과 표를 출력합니다.
static void push_pop(?&Stack<int> s) {
match (s) { case some(st): { st.push(1); st.pop(); } case null: { } }
}
void bench() {
struct Stack<int> st = std::Stack::new();
struct BenchSuite b = std::BenchSuite::new();
unsafe { b.add("stack push/pop", push_pop, (?&Stack<int>)&st, 1000000UL); }
b.run();
b.print_results();
st.free();
}
벤치마크 이름은 BENCH_NAME_MAX(64)자로 잘립니다.
퍼징 (test/fuzz.h)
FuzzTarget(FuzzTarget::new(func, iters)로 생성, func:
fn void([]const unsigned char data)) — 구조를
인식하지 못하는 경량 인프로세스 뮤테이터입니다: run(corpus)는
시드 코퍼스에서 시작해 iters번의 무작위 변이를 적용하고, 각 결과를
대상 함수에 전달합니다. 코퍼스와 각 입력은 바이트 슬라이스입니다. 커버리지 기반이 아니므로 개발 중 간단하고 임시적인
퍼징을 위한 것이지, CI에서 libFuzzer/AFL을 대체하는 용도는 아닙니다. 대상
함수 안에서 발생하는 크래시/패닉은 포착되지 않으며, 일반적인 프로세스
크래시로 그대로 드러납니다.