Lincheck 개요

Lincheck 개요

Lincheck는 JVM에서 동시성 코드를 테스트하기 위한 프레임워크입니다. 테스트를 실행할 때 Lincheck는 프로그램의 가능한 스레드 인터리빙(interleaving)을 탐색하고, 잘못된 동작을 유발하는 인터리빙을 보고해 줍니다.

Kotlin Multiplatform 프로젝트에서는 Lincheck로 JVM에서 실행되는 코드만 테스트할 수 있어요.

Lincheck의 동시성 테스트는 각 스레드에 대한 작업 목록과 기대하는 검증(assertion)만 나열하면 됩니다. 나머지는 Lincheck가 처리합니다:

class CounterTest {
    @Test // Test function declaration
    fun test() = Lincheck.runConcurrentTest {
        var counter = 0

        // Increments the counter concurrently
        val t1 = thread { counter++ }
        val t2 = thread { counter++ }

        // Waits for the threads to finish
        t1.join()
        t2.join()

        // Checks that both increments have been applied
        assertEquals(2, counter)
    }
}

테스트가 실패하면 Lincheck는 오류를 유발한 스레드 인터리빙과 스레드 전환 지점을 알려줍니다:

| ------------------------------------------------------------------------------- |
|                   Main Thread                   |   Thread 1    |   Thread 2    |
| ------------------------------------------------------------------------------- |
| thread(block = Lambda#2): Thread#1              |               |               |
| thread(block = Lambda#3): Thread#2              |               |               |
| switch (reason: waiting for Thread 1 to finish) |               |               |
|                                                 |               | run()         |
|                                                 |               |   counter ➜ 0 |
|                                                 |               |   switch      |
|                                                 | run()         |               |
|                                                 |   counter ➜ 0 |               |
|                                                 |   counter = 1 |               |
|                                                 |               |   counter = 1 |
| Thread#1.join()                                 |               |               |
| Thread#2.join()                                 |               |               |
| counter.element ➜ 1                             |               |               |
| assertEquals(2, 1): threw AssertionFailedError  |               |               |
| ------------------------------------------------------------------------------- |

출처: Overview

본문

JVM이 동시성 코드를 실행할 때마다 스레드 간 작업의 실행 순서가 달라질 수 있어요. 예를 들어 어떤 작업이 다른 스레드의 다른 작업에 의해 중단될 수 있습니다. 이것 자체가 오류는 아니지만, 코드에 동시성 버그가 있으면 오류로 이어질 수 있죠.

실행 시나리오(execution scenario)는 작업이 스레드에 어떻게 분배되는지와 각 스레드 내에서의 실행 순서를 정의합니다.

실행 스케줄(execution schedule, 스레드 인터리빙이라고도 함)은 모든 스레드에 걸친 모든 작업의 실행 순서를 정의합니다.

Lincheck는 잘못된 동작을 유발하는 실행 스케줄을 찾기 위해 두 가지 테스트 전략을 구현합니다:

  • 모델 체킹(Model checking). Lincheck는 프로그램에 명시적인 스레드 전환 명령을 삽입하여 스케줄링을 제어합니다. 이 명령은 동기화 지점이나 공유 메모리 접근 지점에 배치됩니다. 모델 체킹을 통해 Lincheck는 오류를 유발하는 정확한 실행 트레이스를 생성할 수 있어요.
  • 스트레스 테스트(Stress testing). 운영 체제가 스케줄링을 제어합니다. Lincheck는 오류를 찾을 확률을 높이기 위해 각 시나리오를 여러 번 실행합니다.

Lincheck 둘러보기

더 알아보기