|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "[Swift] Type Scanner (2) - Swift Testing을 분석하여 Test 타입 찾기" |
| 4 | +tags: [type scanner, swift, testing, Swift Testing] |
| 5 | +--- |
| 6 | +{% include JB/setup %} |
| 7 | + |
| 8 | +[Test+Discovery.swift#L26](https://github.com/swiftlang/swift-testing/blob/e2ec0411e5f7407fc2d325c9feea8f0ac10a60e2/Sources/Testing/Test%2BDiscovery.swift#L26)를 보면, `__🟠$test_container__` 문자열이 포함된 타입 이름을 찾을려는 것을 `_testContainerTypeNameMagic` 속성 이름을 통해 알 수 있습니다. |
| 9 | + |
| 10 | +<br/> |
| 11 | +<p style="text-align:center;"> |
| 12 | +<img src="{{ site.product_url }}/image/2025/05/01.png"/> |
| 13 | +</p><br/> |
| 14 | + |
| 15 | +또한, 아래 all 속성에서 `enumerateTypes(withNamesContaining:)` 를 이용하여 `__🟠$test_container__` 문자열이 포함된 타입을 추출하여, 해당 타입이 `__TestContainer.Type` 타입인지 체크를 한 뒤, `__tests` 를 꺼내어 `Sequence`에 넣어, 테스트를 수행할려는 것을 확인할 수 있습니다. |
| 16 | + |
| 17 | +`enumerateTypes(withNamesContaining:)` 함수는 [Test+Discovery.swift#L69](https://github.com/swiftlang/swift-testing/blob/e2ec0411e5f7407fc2d325c9feea8f0ac10a60e2/Sources/Testing/Test%2BDiscovery.swift#L69)에서 찾을 수 있습니다. |
| 18 | + |
| 19 | +<br/> |
| 20 | +<p style="text-align:center;"> |
| 21 | +<img src="{{ site.product_url }}/image/2025/05/02.png"/> |
| 22 | +</p><br/> |
| 23 | + |
| 24 | +이 함수에서 실질적인 동작을 수행하는 함수인 `swt_enumerateTypes(withNamesContaining:_:_:)`를 찾아야 합니다. |
| 25 | + |
| 26 | +이 함수는 [Discovery.h#L40](https://github.com/swiftlang/swift-testing/blob/5b4d6d6f7d4e0dbca4dd6593e0c8862022388d7c/Sources/_TestingInternals/include/Discovery.h#L40)에서 찾을 수 있습니다. `swt_enumerateTypesWithNamesContaining` 함수로, Swift에서 이름을 축약해서 사용할 수 있도록 `SWT_SWIFT_NAME(swt_enumerateTypes(withNamesContaining:_:_:))` 코드를 작성한 것을 확인할 수 있습니다. |
| 27 | + |
| 28 | +<br/> |
| 29 | +<p style="text-align:center;"> |
| 30 | +<img src="{{ site.dev_url }}/image/2025/05/03.png"/> |
| 31 | +</p><br/> |
| 32 | + |
| 33 | +이제 `swt_enumerateTypesWithNamesContaining` 함수가 구현된 [Discovery.cpp](https://github.com/swiftlang/swift-testing/blob/5b4d6d6f7d4e0dbca4dd6593e0c8862022388d7c/Sources/_TestingInternals/Discovery.cpp#L509)를 확인해봅시다. |
| 34 | + |
| 35 | +```cpp |
| 36 | +void swt_enumerateTypesWithNamesContaining(const char *nameSubstring, void *context, SWTTypeEnumerator body) { |
| 37 | + enumerateTypeMetadataSections([=] (const SWTSectionBounds<SWTTypeMetadataRecord>& sectionBounds, bool *stop) { |
| 38 | + for (const auto& record : sectionBounds) { |
| 39 | + auto contextDescriptor = record.getContextDescriptor(); |
| 40 | + if (!contextDescriptor) { |
| 41 | + // This type metadata record is invalid (or we don't understand how to |
| 42 | + // get its context descriptor), so skip it. |
| 43 | + continue; |
| 44 | + } else if (contextDescriptor->isGeneric()) { |
| 45 | + // Generic types cannot be fully instantiated without generic |
| 46 | + // parameters, which is not something we can know abstractly. |
| 47 | + continue; |
| 48 | + } |
| 49 | + |
| 50 | + // Check that the type's name passes. This will be more expensive than the |
| 51 | + // checks above, but should be cheaper than realizing the metadata. |
| 52 | + const char *typeName = contextDescriptor->getName(); |
| 53 | + bool nameOK = typeName && nullptr != std::strstr(typeName, nameSubstring); |
| 54 | + if (!nameOK) { |
| 55 | + continue; |
| 56 | + } |
| 57 | + |
| 58 | + if (void *typeMetadata = contextDescriptor->getMetadata()) { |
| 59 | + body(sectionBounds.imageAddress, typeMetadata, stop, context); |
| 60 | + } |
| 61 | + } |
| 62 | + }); |
| 63 | +} |
| 64 | +``` |
| 65 | +
|
| 66 | +`enumerateTypeMetadataSections` 함수를 통해 현재 프로세스에 로드된 Swift 타입 메타데이터 섹션을 열거하고, 각 섹션 내의 모든 타입 메타데이터 레코드를 반복 처리합니다. |
| 67 | +
|
| 68 | +메타데이터 레코드가 유효하지 않거나, 제네릭이면 건너뜁니다. |
| 69 | +
|
| 70 | +`const char *typeName = contextDescriptor->getName();` 에서 타입 이름을 가져와서, 해당 이름에 `__🟠$test_container__` 문자열이 포함되어 있는지 검사하고, 일치하면 타입 메타데이터를 가져와 이미지 주소, 타입 메타데이터, 중지 플래그, 컨텍스트 정보를 전달합니다. |
| 71 | +
|
| 72 | +즉, 타입 이름에 주어진 문자열이 포함되는 Swift 타입들을 찾아 넘겨주는 역할을 합니다. 이 방식은 enum, struct, class 등의 모든 타입을 찾아내기 위에 사용할 수 있습니다. |
| 73 | +
|
| 74 | +이를 이용하면, 타입 메타데이터를 스캔하는 기능을 만들어낼 수 있습니다. |
| 75 | +
|
| 76 | +<br/> |
| 77 | +
|
| 78 | +## 참고자료 |
| 79 | +
|
| 80 | +* [Swift Testing](https://github.com/swiftlang/swift-testing) |
| 81 | +* [Displaying all SwiftUI Previews in a Storybook app](https://medium.com/eureka-engineering/displaying-all-swiftui-previews-in-a-storybook-app-1dd8e925d777) |
| 82 | + * [eure/Storybook-ios](https://github.com/eure/Storybook-ios) |
| 83 | +* GitHub |
| 84 | + * [minsone/DIContainer](https://github.com/minsOne/DIContainer/blob/d331a2c64ceefef5ea67bb0e46d0d0ae71aac750/Sources/DIContainer/Scanner/MachOLoader/MachOLoader.swift) |
| 85 | + * [p-x9/MachOKit](https://github.com/p-x9/MachOKit) |
0 commit comments