@@ -3200,6 +3200,89 @@ class PathHierarchyTests: XCTestCase {
3200
3200
try assertFindsPath ( " /ModuleName/ContainerName " , in: tree, asSymbolID: containerID)
3201
3201
}
3202
3202
3203
+ func testInvalidSymbolGraphWithNoMemberOfRelationshipsDesptiteDeepHierarchyAcrossManyPlatforms( ) throws {
3204
+ // We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid.
3205
+
3206
+ // If the symbol graph files define a deep hierarchy, with the same symbol names but different symbol kinds across different, we try to match them up by language.
3207
+
3208
+ // Repeat the same symbols in both languages for many platforms.
3209
+ let platforms = ( 1 ... 10 ) . map {
3210
+ let name = " Platform \( $0) "
3211
+ return ( name: name, availability: [ makeAvailabilityItem ( domainName: name) ] )
3212
+ }
3213
+
3214
+ let catalog = Folder ( name: " unit-test.docc " , content: [
3215
+ Folder ( name: " clang " , content: platforms. map { platform in
3216
+ return JSONFile ( name: " ModuleName- \( platform. name) .symbols.json " , content: makeSymbolGraph (
3217
+ moduleName: " ModuleName " ,
3218
+ symbols: [
3219
+ makeSymbol ( id: " some-outer-container-id " , language: . objectiveC, kind: . class, pathComponents: [ " OuterContainerName " ] ) ,
3220
+ makeSymbol ( id: " some-middle-container-id " , language: . objectiveC, kind: . class, pathComponents: [ " OuterContainerName " , " MiddleContainerName " ] ) ,
3221
+ makeSymbol ( id: " some-inner-container-id " , language: . objectiveC, kind: . class, pathComponents: [ " OuterContainerName " , " MiddleContainerName " , " InnerContainerName " ] ) ,
3222
+ makeSymbol ( id: " some-objc-specific-member-id " , language: . objectiveC, kind: . property, pathComponents: [ " OuterContainerName " , " MiddleContainerName " , " InnerContainerName " , " objcSpecificMember " ] ) ,
3223
+ ] ,
3224
+ relationships: [ /* all required memberOf relationships all missing */]
3225
+ ) )
3226
+ } ) ,
3227
+
3228
+ Folder ( name: " swift " , content: platforms. map { platform in
3229
+ return JSONFile ( name: " ModuleName- \( platform. name) .symbols.json " , content: makeSymbolGraph (
3230
+ moduleName: " ModuleName " ,
3231
+ symbols: [
3232
+ makeSymbol ( id: " some-outer-container-id " , kind: . struct, pathComponents: [ " OuterContainerName " ] ) ,
3233
+ makeSymbol ( id: " some-middle-container-id " , kind: . struct, pathComponents: [ " OuterContainerName " , " MiddleContainerName " ] ) ,
3234
+ makeSymbol ( id: " some-inner-container-id " , kind: . struct, pathComponents: [ " OuterContainerName " , " MiddleContainerName " , " InnerContainerName " ] ) ,
3235
+ makeSymbol ( id: " some-swift-specific-member-id " , kind: . method, pathComponents: [ " OuterContainerName " , " MiddleContainerName " , " InnerContainerName " , " swiftSpecificMember() " ] ) ,
3236
+ ] ,
3237
+ relationships: [ /* all required memberOf relationships all missing */]
3238
+ ) )
3239
+ } )
3240
+ ] )
3241
+
3242
+ let ( _, context) = try loadBundle ( catalog: catalog)
3243
+ let tree = context. linkResolver. localResolver. pathHierarchy
3244
+
3245
+ let swiftSpecificNode = try tree. findNode ( path: " /ModuleName/OuterContainerName-struct/MiddleContainerName-struct/InnerContainerName-struct/swiftSpecificMember() " , onlyFindSymbols: true , parent: nil )
3246
+ XCTAssertEqual ( swiftSpecificNode. symbol? . identifier. precise, " some-swift-specific-member-id " )
3247
+ // Trace up and check that each node is represented by a symbol
3248
+ XCTAssertEqual ( swiftSpecificNode. parent? . symbol? . identifier. precise, " some-inner-container-id " )
3249
+ XCTAssertEqual ( swiftSpecificNode. parent? . parent? . symbol? . identifier. precise, " some-middle-container-id " )
3250
+ XCTAssertEqual ( swiftSpecificNode. parent? . parent? . parent? . symbol? . identifier. precise, " some-outer-container-id " )
3251
+
3252
+ let objcSpecificNode = try tree. findNode ( path: " /ModuleName/OuterContainerName-class/MiddleContainerName-class/InnerContainerName-class/objcSpecificMember " , onlyFindSymbols: true , parent: nil )
3253
+ XCTAssertEqual ( objcSpecificNode. symbol? . identifier. precise, " some-objc-specific-member-id " )
3254
+ // Trace up and check that each node is represented by a symbol
3255
+ XCTAssertEqual ( objcSpecificNode. parent? . symbol? . identifier. precise, " some-inner-container-id " )
3256
+ XCTAssertEqual ( objcSpecificNode. parent? . parent? . symbol? . identifier. precise, " some-middle-container-id " )
3257
+ XCTAssertEqual ( objcSpecificNode. parent? . parent? . parent? . symbol? . identifier. precise, " some-outer-container-id " )
3258
+
3259
+ // Check that each language has different nodes
3260
+ XCTAssertNotEqual ( swiftSpecificNode. parent? . identifier, objcSpecificNode. parent? . identifier)
3261
+ XCTAssertNotEqual ( swiftSpecificNode. parent? . parent? . identifier, objcSpecificNode. parent? . parent? . identifier)
3262
+ XCTAssertNotEqual ( swiftSpecificNode. parent? . parent? . parent? . identifier, objcSpecificNode. parent? . parent? . parent? . identifier)
3263
+
3264
+ // Check that neither path require disambiguation
3265
+ let paths = tree. caseInsensitiveDisambiguatedPaths ( )
3266
+
3267
+ XCTAssertEqual ( paths [ " some-outer-container-id " ] , " /ModuleName/OuterContainerName " )
3268
+ XCTAssertEqual ( paths [ " some-middle-container-id " ] , " /ModuleName/OuterContainerName/MiddleContainerName " )
3269
+ XCTAssertEqual ( paths [ " some-inner-container-id " ] , " /ModuleName/OuterContainerName/MiddleContainerName/InnerContainerName " )
3270
+ XCTAssertEqual ( paths [ " some-swift-specific-member-id " ] , " /ModuleName/OuterContainerName/MiddleContainerName/InnerContainerName/swiftSpecificMember() " )
3271
+ XCTAssertEqual ( paths [ " some-objc-specific-member-id " ] , " /ModuleName/OuterContainerName/MiddleContainerName/InnerContainerName/objcSpecificMember " )
3272
+
3273
+ // Check that the hierarchy doesn't contain any sparse nodes
3274
+ var remaining = tree. modules [ ... ]
3275
+ XCTAssertFalse ( remaining. isEmpty)
3276
+
3277
+ while let node = remaining. popFirst ( ) {
3278
+ XCTAssertNotNil ( node. symbol, " Unexpected sparse node named ' \( node. name) ' in hierarchy " )
3279
+
3280
+ for container in node. children. values {
3281
+ remaining. append ( contentsOf: container. storage. map ( \. node) )
3282
+ }
3283
+ }
3284
+ }
3285
+
3203
3286
func testMissingReferencedContainerSymbolOnSomePlatforms( ) throws {
3204
3287
// We make a best-effort attempt to create a valid path hierarchy, even if the symbol graph inputs are not valid.
3205
3288
0 commit comments