فهرست منبع

Add support for file-containing-symbol reflection request. (#1675)

Motivation:

The file-containing-symbol request is part of the Reflection Service and enables users to find the proto file
containing a symbol they specify and its transitive dependencies.

Modifications:

Added a dictionary of the fully qualified names of symbols and their corresponding file names, in the ReflectionServiceData struct.
Added the function that creates the server response, after getting the file name corresponding to the symbol name and
getting its tranaitive dependencies. Also, split the tests into Integration and Unit tests, and added tests for the new request.

Result:

Users of the Reflection Service will be able to get from the server the proto file that contains the symbols they are specifying in the request and its transitive dependencies.
Stefana-Ioana Dranca 2 سال پیش
والد
کامیت
4df985f809

+ 74 - 0
Sources/GRPCReflectionService/Server/ReflectionService.swift

@@ -46,10 +46,13 @@ internal struct ReflectionServiceData: Sendable {
 
   internal var fileDescriptorDataByFilename: [String: FileDescriptorProtoData]
   internal var serviceNames: [String]
+  internal var fileNameBySymbol: [String: String]
 
   internal init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
     self.serviceNames = []
     self.fileDescriptorDataByFilename = [:]
+    self.fileNameBySymbol = [:]
+
     for fileDescriptorProto in fileDescriptors {
       let serializedFileDescriptorProto: Data
       do {
@@ -67,6 +70,19 @@ internal struct ReflectionServiceData: Sendable {
       )
       self.fileDescriptorDataByFilename[fileDescriptorProto.name] = protoData
       self.serviceNames.append(contentsOf: fileDescriptorProto.service.map { $0.name })
+      for qualifiedSybolName in fileDescriptorProto.qualifiedSymbolNames {
+        let oldValue = self.fileNameBySymbol.updateValue(
+          fileDescriptorProto.name,
+          forKey: qualifiedSybolName
+        )
+        if let oldValue = oldValue {
+          throw GRPCStatus(
+            code: .alreadyExists,
+            message:
+              "The \(qualifiedSybolName) symbol from \(fileDescriptorProto.name) already exists in \(oldValue)."
+          )
+        }
+      }
     }
   }
 
@@ -99,6 +115,10 @@ internal struct ReflectionServiceData: Sendable {
     }
     return serializedFileDescriptorProtos
   }
+
+  internal func nameOfFileContainingSymbol(named symbolName: String) -> String? {
+    return self.fileNameBySymbol[symbolName]
+  }
 }
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@@ -139,6 +159,19 @@ internal final class ReflectionServiceProvider: Reflection_ServerReflectionAsync
     )
   }
 
+  internal func findFileBySymbol(
+    _ symbolName: String,
+    request: Reflection_ServerReflectionRequest
+  ) throws -> Reflection_ServerReflectionResponse {
+    guard let fileName = self.protoRegistry.nameOfFileContainingSymbol(named: symbolName) else {
+      throw GRPCStatus(
+        code: .notFound,
+        message: "The provided symbol could not be found."
+      )
+    }
+    return try self.findFileByFileName(fileName, request: request)
+  }
+
   internal func serverReflectionInfo(
     requestStream: GRPCAsyncRequestStream<Reflection_ServerReflectionRequest>,
     responseStream: GRPCAsyncResponseStreamWriter<Reflection_ServerReflectionResponse>,
@@ -157,6 +190,13 @@ internal final class ReflectionServiceProvider: Reflection_ServerReflectionAsync
         let response = try self.getServicesNames(request: request)
         try await responseStream.send(response)
 
+      case let .fileContainingSymbol(symbolName):
+        let response = try self.findFileBySymbol(
+          symbolName,
+          request: request
+        )
+        try await responseStream.send(response)
+
       default:
         throw GRPCStatus(code: .unimplemented)
       }
@@ -187,3 +227,37 @@ extension Reflection_ServerReflectionResponse {
     }
   }
 }
+
+extension Google_Protobuf_FileDescriptorProto {
+  var qualifiedServiceAndMethodNames: [String] {
+    var names: [String] = []
+
+    for service in self.service {
+      names.append(self.package + "." + service.name)
+      names.append(
+        contentsOf: service.method
+          .map { self.package + "." + service.name + "." + $0.name }
+      )
+    }
+    return names
+  }
+
+  var qualifiedMessageTypes: [String] {
+    return self.messageType.map {
+      self.package + "." + $0.name
+    }
+  }
+
+  var qualifiedEnumTypes: [String] {
+    return self.enumType.map {
+      self.package + "." + $0.name
+    }
+  }
+
+  var qualifiedSymbolNames: [String] {
+    var names = self.qualifiedServiceAndMethodNames
+    names.append(contentsOf: self.qualifiedMessageTypes)
+    names.append(contentsOf: self.qualifiedEnumTypes)
+    return names
+  }
+}

+ 177 - 0
Tests/GRPCTests/GRPCReflectionServiceTests/ReflectionServiceIntegrationTests.swift

@@ -0,0 +1,177 @@
+/*
+ * Copyright 2023, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import GRPC
+import GRPCReflectionService
+import NIOPosix
+import SwiftProtobuf
+import XCTest
+
+@testable import GRPCReflectionService
+
+final class ReflectionServiceIntegrationTests: GRPCTestCase {
+  private var server: Server?
+  private var channel: GRPCChannel?
+  private let protos: [Google_Protobuf_FileDescriptorProto] = makeProtosWithDependencies()
+  private let independentProto: Google_Protobuf_FileDescriptorProto = generateFileDescriptorProto(
+    fileName: "independentBar",
+    suffix: 5
+  )
+
+  private func setUpServerAndChannel() throws {
+    let reflectionServiceProvider = try ReflectionService(
+      fileDescriptors: self.protos + [self.independentProto]
+    )
+
+    let server = try Server.insecure(group: MultiThreadedEventLoopGroup.singleton)
+      .withServiceProviders([reflectionServiceProvider])
+      .withLogger(self.serverLogger)
+      .bind(host: "127.0.0.1", port: 0)
+      .wait()
+    self.server = server
+
+    let channel = try GRPCChannelPool.with(
+      target: .hostAndPort("127.0.0.1", server.channel.localAddress!.port!),
+      transportSecurity: .plaintext,
+      eventLoopGroup: MultiThreadedEventLoopGroup.singleton
+    ) {
+      $0.backgroundActivityLogger = self.clientLogger
+    }
+
+    self.channel = channel
+  }
+
+  override func tearDown() {
+    if let channel = self.channel {
+      XCTAssertNoThrow(try channel.close().wait())
+    }
+    if let server = self.server {
+      XCTAssertNoThrow(try server.close().wait())
+    }
+
+    super.tearDown()
+  }
+
+  func testFileByFileName() async throws {
+    try self.setUpServerAndChannel()
+    let client = Reflection_ServerReflectionAsyncClient(channel: self.channel!)
+    let serviceReflectionInfo = client.makeServerReflectionInfoCall()
+    try await serviceReflectionInfo.requestStream.send(
+      .with {
+        $0.host = "127.0.0.1"
+        $0.fileByFilename = "bar1.proto"
+      }
+    )
+    serviceReflectionInfo.requestStream.finish()
+
+    var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator()
+    guard let message = try await iterator.next() else {
+      return XCTFail("Could not get a response message.")
+    }
+
+    let receivedFileDescriptorProto =
+      try Google_Protobuf_FileDescriptorProto(
+        serializedData: (message.fileDescriptorResponse
+          .fileDescriptorProto[0])
+      )
+
+    XCTAssertEqual(receivedFileDescriptorProto.name, "bar1.proto")
+    XCTAssertEqual(receivedFileDescriptorProto.service.count, 1)
+
+    guard let service = receivedFileDescriptorProto.service.first else {
+      return XCTFail("The received file descriptor proto doesn't have any services.")
+    }
+    guard let method = service.method.first else {
+      return XCTFail("The service of the received file descriptor proto doesn't have any methods.")
+    }
+    XCTAssertEqual(method.name, "testMethod1")
+    XCTAssertEqual(message.fileDescriptorResponse.fileDescriptorProto.count, 4)
+  }
+
+  func testListServices() async throws {
+    try self.setUpServerAndChannel()
+    let client = Reflection_ServerReflectionAsyncClient(channel: self.channel!)
+    let serviceReflectionInfo = client.makeServerReflectionInfoCall()
+
+    try await serviceReflectionInfo.requestStream.send(
+      .with {
+        $0.host = "127.0.0.1"
+        $0.listServices = "services"
+      }
+    )
+
+    serviceReflectionInfo.requestStream.finish()
+    var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator()
+    guard let message = try await iterator.next() else {
+      return XCTFail("Could not get a response message.")
+    }
+
+    let receivedServices = message.listServicesResponse.service.map { $0.name }.sorted()
+    let servicesNames = (self.protos + [self.independentProto]).serviceNames.sorted()
+
+    XCTAssertEqual(receivedServices, servicesNames)
+  }
+
+  func testFileBySymbol() async throws {
+    try self.setUpServerAndChannel()
+    let client = Reflection_ServerReflectionAsyncClient(channel: self.channel!)
+    let serviceReflectionInfo = client.makeServerReflectionInfoCall()
+
+    try await serviceReflectionInfo.requestStream.send(
+      .with {
+        $0.host = "127.0.0.1"
+        $0.fileContainingSymbol = "packagebar1.enumType1"
+      }
+    )
+
+    serviceReflectionInfo.requestStream.finish()
+    var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator()
+    guard let message = try await iterator.next() else {
+      return XCTFail("Could not get a response message.")
+    }
+    let receivedData: [Google_Protobuf_FileDescriptorProto]
+    do {
+      receivedData = try message.fileDescriptorResponse.fileDescriptorProto.map {
+        try Google_Protobuf_FileDescriptorProto(serializedData: $0)
+      }
+    } catch {
+      return XCTFail("Could not serialize data received as a message.")
+    }
+
+    let fileToFind = self.protos[0]
+    let dependentProtos = self.protos[1...]
+    for fileDescriptorProto in receivedData {
+      if fileDescriptorProto == fileToFind {
+        XCTAssert(
+          fileDescriptorProto.enumType.names.contains("enumType1"),
+          """
+          The response doesn't contain the serialized file descriptor proto \
+          containing the \"packagebar1.enumType1\" symbol.
+          """
+        )
+      } else {
+        XCTAssert(
+          dependentProtos.contains(fileDescriptorProto),
+          """
+          The \(fileDescriptorProto.name) is not a dependency of the \
+          proto file containing the \"packagebar1.enumType1\" symbol.
+          """
+        )
+      }
+    }
+  }
+}

+ 102 - 192
Tests/GRPCTests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift → Tests/GRPCTests/GRPCReflectionServiceTests/ReflectionServiceUnitTests.swift

@@ -17,228 +17,144 @@
 import Foundation
 import GRPC
 import GRPCReflectionService
-import NIOPosix
 import SwiftProtobuf
 import XCTest
 
 @testable import GRPCReflectionService
 
-final class GRPCReflectionServiceTests: GRPCTestCase {
-  private var group: MultiThreadedEventLoopGroup?
-  private var server: Server?
-  private var channel: GRPCChannel?
-
-  private func generateProto(name: String, id: Int) -> Google_Protobuf_FileDescriptorProto {
-    let inputMessage = Google_Protobuf_DescriptorProto.with {
-      $0.name = "inputMessage"
-      $0.field = [
-        Google_Protobuf_FieldDescriptorProto.with {
-          $0.name = "inputField"
-          $0.type = .bool
-        }
-      ]
-    }
-
-    let outputMessage = Google_Protobuf_DescriptorProto.with {
-      $0.name = "outputMessage"
-      $0.field = [
-        Google_Protobuf_FieldDescriptorProto.with {
-          $0.name = "outputField"
-          $0.type = .int32
-        }
-      ]
-    }
-
-    let method = Google_Protobuf_MethodDescriptorProto.with {
-      $0.name = "testMethod" + String(id)
-      $0.inputType = inputMessage.name
-      $0.outputType = outputMessage.name
-    }
-
-    let serviceDescriptor = Google_Protobuf_ServiceDescriptorProto.with {
-      $0.method = [method]
-      $0.name = "service" + String(id)
-    }
+final class ReflectionServiceUnitTests: GRPCTestCase {
+  /// Testing the fileDescriptorDataByFilename dictionary of the ReflectionServiceData object.
+  func testFileDescriptorDataByFilename() throws {
+    var protos = makeProtosWithDependencies()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
 
-    let fileDescriptorProto = Google_Protobuf_FileDescriptorProto.with {
-      $0.service = [serviceDescriptor]
-      $0.name = name + String(id) + ".proto"
-      $0.messageType = [inputMessage, outputMessage]
-    }
+    let registryFileDescriptorData = registry.fileDescriptorDataByFilename
 
-    return fileDescriptorProto
-  }
+    for (fileName, protoData) in registryFileDescriptorData {
+      let serializedFiledescriptorData = protoData.serializedFileDescriptorProto
+      let dependencyFileNames = protoData.dependencyFileNames
 
-  /// Creates the dependencies of the proto used in the testing context.
-  private func makeProtosWithDependencies() -> [Google_Protobuf_FileDescriptorProto] {
-    var fileDependencies: [Google_Protobuf_FileDescriptorProto] = []
-    for id in 1 ... 4 {
-      let fileDescriptorProto = self.generateProto(name: "bar", id: id)
-      if id != 1 {
-        // Dependency of the first dependency.
-        fileDependencies[0].dependency.append(fileDescriptorProto.name)
+      guard let index = protos.firstIndex(where: { $0.name == fileName }) else {
+        return XCTFail(
+          """
+          Could not find the file descriptor proto of \(fileName) \
+          in the original file descriptor protos list.
+          """
+        )
       }
-      fileDependencies.append(fileDescriptorProto)
-    }
-    return fileDependencies
-  }
 
-  private func makeProtosWithComplexDependencies() -> [Google_Protobuf_FileDescriptorProto] {
-    var protos: [Google_Protobuf_FileDescriptorProto] = []
-    protos.append(self.generateProto(name: "foo", id: 0))
-    for id in 1 ... 10 {
-      let fileDescriptorProtoA = self.generateProto(name: "fooA", id: id)
-      let fileDescriptorProtoB = self.generateProto(name: "fooB", id: id)
-      let parent = protos.count > 1 ? protos.count - Int.random(in: 1 ..< 3) : protos.count - 1
-      protos[parent].dependency.append(fileDescriptorProtoA.name)
-      protos[parent].dependency.append(fileDescriptorProtoB.name)
-      protos.append(fileDescriptorProtoA)
-      protos.append(fileDescriptorProtoB)
+      let originalProto = protos[index]
+      XCTAssertEqual(originalProto.name, fileName)
+      XCTAssertEqual(try originalProto.serializedData(), serializedFiledescriptorData)
+      XCTAssertEqual(originalProto.dependency, dependencyFileNames)
+
+      protos.remove(at: index)
     }
-    return protos
+    XCTAssert(protos.isEmpty)
   }
 
-  private func getServicesNamesFromProtos(
-    protos: [Google_Protobuf_FileDescriptorProto]
-  ) -> [String] {
-    return protos.serviceNames
+  /// Testing the serviceNames array of the ReflectionServiceData object.
+  func testServiceNames() throws {
+    let protos = makeProtosWithDependencies()
+    let servicesNames = protos.serviceNames.sorted()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let registryServices = registry.serviceNames.sorted()
+    XCTAssertEqual(registryServices, servicesNames)
   }
 
-  private func setUpServerAndChannel() throws {
-    let reflectionServiceProvider = try ReflectionService(
-      fileDescriptors: self.makeProtosWithDependencies()
-    )
-
-    let server = try Server.insecure(group: MultiThreadedEventLoopGroup.singleton)
-      .withServiceProviders([reflectionServiceProvider])
-      .withLogger(self.serverLogger)
-      .bind(host: "127.0.0.1", port: 0)
-      .wait()
-    self.server = server
-
-    let channel = try GRPCChannelPool.with(
-      target: .hostAndPort("127.0.0.1", server.channel.localAddress!.port!),
-      transportSecurity: .plaintext,
-      eventLoopGroup: MultiThreadedEventLoopGroup.singleton
-    ) {
-      $0.backgroundActivityLogger = self.clientLogger
-    }
+  /// Testing the fileNameBySymbol array of the ReflectionServiceData object.
+  func testFileNameBySymbol() throws {
+    let protos = makeProtosWithDependencies()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let registryFileNameBySymbol = registry.fileNameBySymbol
 
-    self.channel = channel
-  }
+    var symbolsCount = 0
 
-  override func tearDown() {
-    if let channel = self.channel {
-      XCTAssertNoThrow(try channel.close().wait())
-    }
-    if let server = self.server {
-      XCTAssertNoThrow(try server.close().wait())
+    for proto in protos {
+      let qualifiedSymbolNames = proto.qualifiedSymbolNames
+      symbolsCount += qualifiedSymbolNames.count
+      for qualifiedSymbolName in qualifiedSymbolNames {
+        XCTAssertEqual(registryFileNameBySymbol[qualifiedSymbolName], proto.name)
+      }
     }
 
-    super.tearDown()
+    XCTAssertEqual(symbolsCount, registryFileNameBySymbol.count)
   }
 
-  func testFileByFileName() async throws {
-    try self.setUpServerAndChannel()
-    let client = Reflection_ServerReflectionAsyncClient(channel: self.channel!)
-    let serviceReflectionInfo = client.makeServerReflectionInfoCall()
-    try await serviceReflectionInfo.requestStream.send(
-      .with {
-        $0.host = "127.0.0.1"
-        $0.fileByFilename = "bar1.proto"
+  func testFileNameBySymbolDuplicatedSymbol() throws {
+    var protos = makeProtosWithDependencies()
+    protos[1].messageType.append(
+      Google_Protobuf_DescriptorProto.with {
+        $0.name = "inputMessage"
+        $0.field = [
+          Google_Protobuf_FieldDescriptorProto.with {
+            $0.name = "inputField"
+            $0.type = .bool
+          }
+        ]
       }
     )
-    serviceReflectionInfo.requestStream.finish()
 
-    var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator()
-    guard let message = try await iterator.next() else {
-      return XCTFail("Could not get a response message.")
-    }
-
-    let receivedFileDescriptorProto =
-      try Google_Protobuf_FileDescriptorProto(
-        serializedData: (message.fileDescriptorResponse
-          .fileDescriptorProto[0])
+    XCTAssertThrowsError(
+      try ReflectionServiceData(fileDescriptors: protos)
+    ) { error in
+      XCTAssertEqual(
+        error as? GRPCStatus,
+        GRPCStatus(
+          code: .alreadyExists,
+          message:
+            """
+            The packagebar2.inputMessage symbol from bar2.proto \
+            already exists in bar2.proto.
+            """
+        )
       )
-
-    XCTAssertEqual(receivedFileDescriptorProto.name, "bar1.proto")
-    XCTAssertEqual(receivedFileDescriptorProto.service.count, 1)
-
-    guard let service = receivedFileDescriptorProto.service.first else {
-      return XCTFail("The received file descriptor proto doesn't have any services.")
     }
-    guard let method = service.method.first else {
-      return XCTFail("The service of the received file descriptor proto doesn't have any methods.")
-    }
-    XCTAssertEqual(method.name, "testMethod1")
-    XCTAssertEqual(message.fileDescriptorResponse.fileDescriptorProto.count, 4)
   }
 
-  func testListServices() async throws {
-    try self.setUpServerAndChannel()
-    let client = Reflection_ServerReflectionAsyncClient(channel: self.channel!)
-    let serviceReflectionInfo = client.makeServerReflectionInfoCall()
-
-    try await serviceReflectionInfo.requestStream.send(
-      .with {
-        $0.host = "127.0.0.1"
-        $0.listServices = "services"
-      }
-    )
-
-    serviceReflectionInfo.requestStream.finish()
-    var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator()
-    guard let message = try await iterator.next() else {
-      return XCTFail("Could not get a response message.")
-    }
-
-    let receivedServices = message.listServicesResponse.service.map { $0.name }.sorted()
-    let servicesNames = self.getServicesNamesFromProtos(
-      protos: self.makeProtosWithDependencies()
-    ).sorted()
+  // Testing the nameOfFileContainingSymbol method for different types of symbols.
 
-    XCTAssertEqual(receivedServices, servicesNames)
+  func testNameOfFileContainingSymbolEnum() throws {
+    let protos = makeProtosWithDependencies()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let fileName = registry.nameOfFileContainingSymbol(named: "packagebar2.enumType2")
+    XCTAssertEqual(fileName, "bar2.proto")
   }
 
-  func testReflectionServiceDataFileDescriptorDataByFilename() throws {
-    var protos = self.makeProtosWithDependencies()
+  func testNameOfFileContainingSymbolMessage() throws {
+    let protos = makeProtosWithDependencies()
     let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let fileName = registry.nameOfFileContainingSymbol(named: "packagebar1.inputMessage")
+    XCTAssertEqual(fileName, "bar1.proto")
+  }
 
-    let registryFileDescriptorData = registry.fileDescriptorDataByFilename
-
-    for (fileName, protoData) in registryFileDescriptorData {
-      let serializedFiledescriptorData = protoData.serializedFileDescriptorProto
-      let dependencyFileNames = protoData.dependencyFileNames
-
-      guard let index = protos.firstIndex(where: { $0.name == fileName }) else {
-        return XCTFail(
-          """
-          Could not find the file descriptor proto of \(fileName) \
-          in the original file descriptor protos list.
-          """
-        )
-      }
-
-      let originalProto = protos[index]
-      XCTAssertEqual(originalProto.name, fileName)
-      XCTAssertEqual(try originalProto.serializedData(), serializedFiledescriptorData)
-      XCTAssertEqual(originalProto.dependency, dependencyFileNames)
+  func testNameOfFileContainingSymbolService() throws {
+    let protos = makeProtosWithDependencies()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let fileName = registry.nameOfFileContainingSymbol(named: "packagebar3.service3")
+    XCTAssertEqual(fileName, "bar3.proto")
+  }
 
-      protos.remove(at: index)
-    }
-    XCTAssert(protos.isEmpty)
+  func testNameOfFileContainingSymbolMethod() throws {
+    let protos = makeProtosWithDependencies()
+    let registry = try ReflectionServiceData(fileDescriptors: protos)
+    let fileName = registry.nameOfFileContainingSymbol(
+      named: "packagebar4.service4.testMethod4"
+    )
+    XCTAssertEqual(fileName, "bar4.proto")
   }
 
-  func testReflectionServiceServicesNames() throws {
-    let protos = self.makeProtosWithDependencies()
-    let servicesNames = self.getServicesNamesFromProtos(protos: protos).sorted()
+  func testNameOfFileContainingSymbolNonExistentSymbol() throws {
+    let protos = makeProtosWithDependencies()
     let registry = try ReflectionServiceData(fileDescriptors: protos)
-    let registryServices = registry.serviceNames.sorted()
-    XCTAssertEqual(registryServices, servicesNames)
+    let fileName = registry.nameOfFileContainingSymbol(named: "packagebar2.enumType3")
+    XCTAssertEqual(fileName, nil)
   }
 
+  // Testing the serializedFileDescriptorProto method in different cases.
+
   func testSerialisedFileDescriptorProtosForDependenciesOfFile() throws {
-    var protos = self.makeProtosWithDependencies()
+    var protos = makeProtosWithDependencies()
     let registry = try ReflectionServiceData(fileDescriptors: protos)
     let serializedFileDescriptorProtos =
       try registry
@@ -285,7 +201,7 @@ final class GRPCReflectionServiceTests: GRPCTestCase {
   }
 
   func testSerialisedFileDescriptorProtosForDependenciesOfFileComplexDependencyGraph() throws {
-    var protos = self.makeProtosWithComplexDependencies()
+    var protos = makeProtosWithComplexDependencies()
     let registry = try ReflectionServiceData(fileDescriptors: protos)
     let serializedFileDescriptorProtos =
       try registry
@@ -332,7 +248,7 @@ final class GRPCReflectionServiceTests: GRPCTestCase {
   }
 
   func testSerialisedFileDescriptorProtosForDependenciesOfFileDependencyLoops() throws {
-    var protos = self.makeProtosWithDependencies()
+    var protos = makeProtosWithDependencies()
     // Making dependencies of the "bar1.proto" to depend on "bar1.proto".
     protos[1].dependency.append("bar1.proto")
     protos[2].dependency.append("bar1.proto")
@@ -382,7 +298,7 @@ final class GRPCReflectionServiceTests: GRPCTestCase {
   }
 
   func testSerialisedFileDescriptorProtosForDependenciesOfFileInvalidFile() throws {
-    let protos = self.makeProtosWithDependencies()
+    let protos = makeProtosWithDependencies()
     let registry = try ReflectionServiceData(fileDescriptors: protos)
     XCTAssertThrowsError(
       try registry.serialisedFileDescriptorProtosForDependenciesOfFile(named: "invalid.proto")
@@ -398,7 +314,7 @@ final class GRPCReflectionServiceTests: GRPCTestCase {
   }
 
   func testSerialisedFileDescriptorProtosForDependenciesOfFileDependencyNotProto() throws {
-    var protos = self.makeProtosWithDependencies()
+    var protos = makeProtosWithDependencies()
     protos[0].dependency.append("invalidDependency")
     let registry = try ReflectionServiceData(fileDescriptors: protos)
     XCTAssertThrowsError(
@@ -414,9 +330,3 @@ final class GRPCReflectionServiceTests: GRPCTestCase {
     }
   }
 }
-
-extension Sequence where Element == Google_Protobuf_FileDescriptorProto {
-  var serviceNames: [String] {
-    self.flatMap { $0.service.map { $0.name } }
-  }
-}

+ 118 - 0
Tests/GRPCTests/GRPCReflectionServiceTests/Utils.swift

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import GRPC
+import SwiftProtobuf
+
+internal func generateFileDescriptorProto(
+  fileName name: String,
+  suffix id: Int
+) -> Google_Protobuf_FileDescriptorProto {
+  let inputMessage = Google_Protobuf_DescriptorProto.with {
+    $0.name = "inputMessage"
+    $0.field = [
+      Google_Protobuf_FieldDescriptorProto.with {
+        $0.name = "inputField"
+        $0.type = .bool
+      }
+    ]
+  }
+
+  let outputMessage = Google_Protobuf_DescriptorProto.with {
+    $0.name = "outputMessage"
+    $0.field = [
+      Google_Protobuf_FieldDescriptorProto.with {
+        $0.name = "outputField"
+        $0.type = .int32
+      }
+    ]
+  }
+
+  let enumType = Google_Protobuf_EnumDescriptorProto.with {
+    $0.name = "enumType" + String(id)
+    $0.value = [
+      Google_Protobuf_EnumValueDescriptorProto.with {
+        $0.name = "value1"
+      },
+      Google_Protobuf_EnumValueDescriptorProto.with {
+        $0.name = "value2"
+      },
+    ]
+  }
+
+  let method = Google_Protobuf_MethodDescriptorProto.with {
+    $0.name = "testMethod" + String(id)
+    $0.inputType = inputMessage.name
+    $0.outputType = outputMessage.name
+  }
+
+  let serviceDescriptor = Google_Protobuf_ServiceDescriptorProto.with {
+    $0.method = [method]
+    $0.name = "service" + String(id)
+  }
+
+  let fileDescriptorProto = Google_Protobuf_FileDescriptorProto.with {
+    $0.service = [serviceDescriptor]
+    $0.name = name + String(id) + ".proto"
+    $0.package = "package" + name + String(id)
+    $0.messageType = [inputMessage, outputMessage]
+    $0.enumType = [enumType]
+  }
+
+  return fileDescriptorProto
+}
+
+/// Creates the dependencies of the proto used in the testing context.
+internal func makeProtosWithDependencies() -> [Google_Protobuf_FileDescriptorProto] {
+  var fileDependencies: [Google_Protobuf_FileDescriptorProto] = []
+  for id in 1 ... 4 {
+    let fileDescriptorProto = generateFileDescriptorProto(fileName: "bar", suffix: id)
+    if id != 1 {
+      // Dependency of the first dependency.
+      fileDependencies[0].dependency.append(fileDescriptorProto.name)
+    }
+    fileDependencies.append(fileDescriptorProto)
+  }
+  return fileDependencies
+}
+
+internal func makeProtosWithComplexDependencies() -> [Google_Protobuf_FileDescriptorProto] {
+  var protos: [Google_Protobuf_FileDescriptorProto] = []
+  protos.append(generateFileDescriptorProto(fileName: "foo", suffix: 0))
+  for id in 1 ... 10 {
+    let fileDescriptorProtoA = generateFileDescriptorProto(fileName: "fooA", suffix: id)
+    let fileDescriptorProtoB = generateFileDescriptorProto(fileName: "fooB", suffix: id)
+    let parent = protos.count > 1 ? protos.count - Int.random(in: 1 ..< 3) : protos.count - 1
+    protos[parent].dependency.append(fileDescriptorProtoA.name)
+    protos[parent].dependency.append(fileDescriptorProtoB.name)
+    protos.append(fileDescriptorProtoA)
+    protos.append(fileDescriptorProtoB)
+  }
+  return protos
+}
+
+extension Sequence where Element == Google_Protobuf_FileDescriptorProto {
+  var serviceNames: [String] {
+    self.flatMap { $0.service.map { $0.name } }
+  }
+}
+
+extension Sequence where Element == Google_Protobuf_EnumDescriptorProto {
+  var names: [String] {
+    self.map { $0.name }
+  }
+}