SnippetBasedTranslatorTests.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /*
  2. * Copyright 2023, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #if os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process)
  17. import XCTest
  18. @testable import GRPCCodeGen
  19. final class SnippetBasedTranslatorTests: XCTestCase {
  20. typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
  21. typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor
  22. func testTypealiasTranslator() throws {
  23. let method = MethodDescriptor(
  24. documentation: "Documentation for MethodA",
  25. name: "MethodA",
  26. isInputStreaming: false,
  27. isOutputStreaming: false,
  28. inputType: "NamespaceA_ServiceARequest",
  29. outputType: "NamespaceA_ServiceAResponse"
  30. )
  31. let service = ServiceDescriptor(
  32. documentation: "Documentation for ServiceA",
  33. name: "ServiceA",
  34. namespace: "namespaceA",
  35. methods: [method]
  36. )
  37. let expectedSwift =
  38. """
  39. enum namespaceA {
  40. enum ServiceA {
  41. enum Methods {
  42. enum MethodA {
  43. typealias Input = NamespaceA_ServiceARequest
  44. typealias Output = NamespaceA_ServiceAResponse
  45. static let descriptor = MethodDescriptor(
  46. service: "namespaceA.ServiceA",
  47. method: "MethodA"
  48. )
  49. }
  50. }
  51. static let methods: [MethodDescriptor] = [
  52. Methods.MethodA.descriptor
  53. ]
  54. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  55. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  56. }
  57. }
  58. """
  59. try self.assertTypealiasTranslation(
  60. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  61. expectedSwift: expectedSwift
  62. )
  63. }
  64. func testTypealiasTranslatorEmptyNamespace() throws {
  65. let method = MethodDescriptor(
  66. documentation: "Documentation for MethodA",
  67. name: "MethodA",
  68. isInputStreaming: false,
  69. isOutputStreaming: false,
  70. inputType: "ServiceARequest",
  71. outputType: "ServiceAResponse"
  72. )
  73. let service = ServiceDescriptor(
  74. documentation: "Documentation for ServiceA",
  75. name: "ServiceA",
  76. namespace: "",
  77. methods: [method]
  78. )
  79. let expectedSwift =
  80. """
  81. enum ServiceA {
  82. enum Methods {
  83. enum MethodA {
  84. typealias Input = ServiceARequest
  85. typealias Output = ServiceAResponse
  86. static let descriptor = MethodDescriptor(
  87. service: "ServiceA",
  88. method: "MethodA"
  89. )
  90. }
  91. }
  92. static let methods: [MethodDescriptor] = [
  93. Methods.MethodA.descriptor
  94. ]
  95. typealias StreamingServiceProtocol = ServiceAServiceStreamingProtocol
  96. typealias ServiceProtocol = ServiceAServiceProtocol
  97. }
  98. """
  99. try self.assertTypealiasTranslation(
  100. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  101. expectedSwift: expectedSwift
  102. )
  103. }
  104. func testTypealiasTranslatorCheckMethodsOrder() throws {
  105. let methodA = MethodDescriptor(
  106. documentation: "Documentation for MethodA",
  107. name: "MethodA",
  108. isInputStreaming: false,
  109. isOutputStreaming: false,
  110. inputType: "NamespaceA_ServiceARequest",
  111. outputType: "NamespaceA_ServiceAResponse"
  112. )
  113. let methodB = MethodDescriptor(
  114. documentation: "Documentation for MethodB",
  115. name: "MethodB",
  116. isInputStreaming: false,
  117. isOutputStreaming: false,
  118. inputType: "NamespaceA_ServiceARequest",
  119. outputType: "NamespaceA_ServiceAResponse"
  120. )
  121. let service = ServiceDescriptor(
  122. documentation: "Documentation for ServiceA",
  123. name: "ServiceA",
  124. namespace: "namespaceA",
  125. methods: [methodA, methodB]
  126. )
  127. let expectedSwift =
  128. """
  129. enum namespaceA {
  130. enum ServiceA {
  131. enum Methods {
  132. enum MethodA {
  133. typealias Input = NamespaceA_ServiceARequest
  134. typealias Output = NamespaceA_ServiceAResponse
  135. static let descriptor = MethodDescriptor(
  136. service: "namespaceA.ServiceA",
  137. method: "MethodA"
  138. )
  139. }
  140. enum MethodB {
  141. typealias Input = NamespaceA_ServiceARequest
  142. typealias Output = NamespaceA_ServiceAResponse
  143. static let descriptor = MethodDescriptor(
  144. service: "namespaceA.ServiceA",
  145. method: "MethodB"
  146. )
  147. }
  148. }
  149. static let methods: [MethodDescriptor] = [
  150. Methods.MethodA.descriptor,
  151. Methods.MethodB.descriptor
  152. ]
  153. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  154. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  155. }
  156. }
  157. """
  158. try self.assertTypealiasTranslation(
  159. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  160. expectedSwift: expectedSwift
  161. )
  162. }
  163. func testTypealiasTranslatorNoMethodsService() throws {
  164. let service = ServiceDescriptor(
  165. documentation: "Documentation for ServiceA",
  166. name: "ServiceA",
  167. namespace: "namespaceA",
  168. methods: []
  169. )
  170. let expectedSwift =
  171. """
  172. enum namespaceA {
  173. enum ServiceA {
  174. enum Methods {}
  175. static let methods: [MethodDescriptor] = []
  176. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  177. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  178. }
  179. }
  180. """
  181. try self.assertTypealiasTranslation(
  182. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  183. expectedSwift: expectedSwift
  184. )
  185. }
  186. func testTypealiasTranslatorServiceAlphabeticalOrder() throws {
  187. let serviceB = ServiceDescriptor(
  188. documentation: "Documentation for BService",
  189. name: "BService",
  190. namespace: "namespacea",
  191. methods: []
  192. )
  193. let serviceA = ServiceDescriptor(
  194. documentation: "Documentation for AService",
  195. name: "AService",
  196. namespace: "namespacea",
  197. methods: []
  198. )
  199. let expectedSwift =
  200. """
  201. enum namespacea {
  202. enum AService {
  203. enum Methods {}
  204. static let methods: [MethodDescriptor] = []
  205. typealias StreamingServiceProtocol = namespacea_AServiceServiceStreamingProtocol
  206. typealias ServiceProtocol = namespacea_AServiceServiceProtocol
  207. }
  208. enum BService {
  209. enum Methods {}
  210. static let methods: [MethodDescriptor] = []
  211. typealias StreamingServiceProtocol = namespacea_BServiceServiceStreamingProtocol
  212. typealias ServiceProtocol = namespacea_BServiceServiceProtocol
  213. }
  214. }
  215. """
  216. try self.assertTypealiasTranslation(
  217. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  218. expectedSwift: expectedSwift
  219. )
  220. }
  221. func testTypealiasTranslatorServiceAlphabeticalOrderNoNamespace() throws {
  222. let serviceB = ServiceDescriptor(
  223. documentation: "Documentation for BService",
  224. name: "BService",
  225. namespace: "",
  226. methods: []
  227. )
  228. let serviceA = ServiceDescriptor(
  229. documentation: "Documentation for AService",
  230. name: "AService",
  231. namespace: "",
  232. methods: []
  233. )
  234. let expectedSwift =
  235. """
  236. enum AService {
  237. enum Methods {}
  238. static let methods: [MethodDescriptor] = []
  239. typealias StreamingServiceProtocol = AServiceServiceStreamingProtocol
  240. typealias ServiceProtocol = AServiceServiceProtocol
  241. }
  242. enum BService {
  243. enum Methods {}
  244. static let methods: [MethodDescriptor] = []
  245. typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol
  246. typealias ServiceProtocol = BServiceServiceProtocol
  247. }
  248. """
  249. try self.assertTypealiasTranslation(
  250. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  251. expectedSwift: expectedSwift
  252. )
  253. }
  254. func testTypealiasTranslatorNamespaceAlphabeticalOrder() throws {
  255. let serviceB = ServiceDescriptor(
  256. documentation: "Documentation for BService",
  257. name: "BService",
  258. namespace: "bnamespace",
  259. methods: []
  260. )
  261. let serviceA = ServiceDescriptor(
  262. documentation: "Documentation for AService",
  263. name: "AService",
  264. namespace: "anamespace",
  265. methods: []
  266. )
  267. let expectedSwift =
  268. """
  269. enum anamespace {
  270. enum AService {
  271. enum Methods {}
  272. static let methods: [MethodDescriptor] = []
  273. typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol
  274. typealias ServiceProtocol = anamespace_AServiceServiceProtocol
  275. }
  276. }
  277. enum bnamespace {
  278. enum BService {
  279. enum Methods {}
  280. static let methods: [MethodDescriptor] = []
  281. typealias StreamingServiceProtocol = bnamespace_BServiceServiceStreamingProtocol
  282. typealias ServiceProtocol = bnamespace_BServiceServiceProtocol
  283. }
  284. }
  285. """
  286. try self.assertTypealiasTranslation(
  287. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  288. expectedSwift: expectedSwift
  289. )
  290. }
  291. func testTypealiasTranslatorNamespaceNoNamespaceOrder() throws {
  292. let serviceA = ServiceDescriptor(
  293. documentation: "Documentation for AService",
  294. name: "AService",
  295. namespace: "anamespace",
  296. methods: []
  297. )
  298. let serviceB = ServiceDescriptor(
  299. documentation: "Documentation for BService",
  300. name: "BService",
  301. namespace: "",
  302. methods: []
  303. )
  304. let expectedSwift =
  305. """
  306. enum BService {
  307. enum Methods {}
  308. static let methods: [MethodDescriptor] = []
  309. typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol
  310. typealias ServiceProtocol = BServiceServiceProtocol
  311. }
  312. enum anamespace {
  313. enum AService {
  314. enum Methods {}
  315. static let methods: [MethodDescriptor] = []
  316. typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol
  317. typealias ServiceProtocol = anamespace_AServiceServiceProtocol
  318. }
  319. }
  320. """
  321. try self.assertTypealiasTranslation(
  322. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceA, serviceB]),
  323. expectedSwift: expectedSwift
  324. )
  325. }
  326. func testTypealiasTranslatorSameNameServicesNoNamespaceError() throws {
  327. let serviceA = ServiceDescriptor(
  328. documentation: "Documentation for AService",
  329. name: "AService",
  330. namespace: "",
  331. methods: []
  332. )
  333. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA])
  334. let translator = TypealiasTranslator()
  335. self.assertThrowsError(
  336. ofType: CodeGenError.self,
  337. try translator.translate(from: codeGenerationRequest)
  338. ) {
  339. error in
  340. XCTAssertEqual(
  341. error as CodeGenError,
  342. CodeGenError(
  343. code: .nonUniqueServiceName,
  344. message: """
  345. Services in an empty namespace must have unique names. \
  346. AService is used as a name for multiple services without namespaces.
  347. """
  348. )
  349. )
  350. }
  351. }
  352. func testTypealiasTranslatorSameNameServicesSameNamespaceError() throws {
  353. let serviceA = ServiceDescriptor(
  354. documentation: "Documentation for AService",
  355. name: "AService",
  356. namespace: "namespacea",
  357. methods: []
  358. )
  359. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA])
  360. let translator = TypealiasTranslator()
  361. self.assertThrowsError(
  362. ofType: CodeGenError.self,
  363. try translator.translate(from: codeGenerationRequest)
  364. ) {
  365. error in
  366. XCTAssertEqual(
  367. error as CodeGenError,
  368. CodeGenError(
  369. code: .nonUniqueServiceName,
  370. message: """
  371. Services within the same namespace must have unique names. \
  372. AService is used as a name for multiple services in the namespacea namespace.
  373. """
  374. )
  375. )
  376. }
  377. }
  378. func testTypealiasTranslatorSameNameMethodsSameServiceError() throws {
  379. let methodA = MethodDescriptor(
  380. documentation: "Documentation for MethodA",
  381. name: "MethodA",
  382. isInputStreaming: false,
  383. isOutputStreaming: false,
  384. inputType: "NamespaceA_ServiceARequest",
  385. outputType: "NamespaceA_ServiceAResponse"
  386. )
  387. let service = ServiceDescriptor(
  388. documentation: "Documentation for AService",
  389. name: "AService",
  390. namespace: "namespacea",
  391. methods: [methodA, methodA]
  392. )
  393. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [service])
  394. let translator = TypealiasTranslator()
  395. self.assertThrowsError(
  396. ofType: CodeGenError.self,
  397. try translator.translate(from: codeGenerationRequest)
  398. ) {
  399. error in
  400. XCTAssertEqual(
  401. error as CodeGenError,
  402. CodeGenError(
  403. code: .nonUniqueMethodName,
  404. message: """
  405. Methods of a service must have unique names. \
  406. MethodA is used as a name for multiple methods of the AService service.
  407. """
  408. )
  409. )
  410. }
  411. }
  412. func testTypealiasTranslatorSameNameNoNamespaceServiceAndNamespaceError() throws {
  413. let serviceA = ServiceDescriptor(
  414. documentation: "Documentation for SameName service with no namespace",
  415. name: "SameName",
  416. namespace: "",
  417. methods: []
  418. )
  419. let serviceB = ServiceDescriptor(
  420. documentation: "Documentation for BService",
  421. name: "BService",
  422. namespace: "SameName",
  423. methods: []
  424. )
  425. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceB])
  426. let translator = TypealiasTranslator()
  427. self.assertThrowsError(
  428. ofType: CodeGenError.self,
  429. try translator.translate(from: codeGenerationRequest)
  430. ) {
  431. error in
  432. XCTAssertEqual(
  433. error as CodeGenError,
  434. CodeGenError(
  435. code: .nonUniqueServiceName,
  436. message: """
  437. Services with no namespace must not have the same names as the namespaces. \
  438. SameName is used as a name for a service with no namespace and a namespace.
  439. """
  440. )
  441. )
  442. }
  443. }
  444. }
  445. extension SnippetBasedTranslatorTests {
  446. private func assertTypealiasTranslation(
  447. codeGenerationRequest: CodeGenerationRequest,
  448. expectedSwift: String
  449. ) throws {
  450. let translator = TypealiasTranslator()
  451. let codeBlocks = try translator.translate(from: codeGenerationRequest)
  452. let renderer = TextBasedRenderer.default
  453. renderer.renderCodeBlocks(codeBlocks)
  454. let contents = renderer.renderedContents()
  455. try XCTAssertEqualWithDiff(contents, expectedSwift)
  456. }
  457. private func assertThrowsError<T, E: Error>(
  458. ofType: E.Type,
  459. _ expression: @autoclosure () throws -> T,
  460. _ errorHandler: (E) -> Void
  461. ) {
  462. XCTAssertThrowsError(try expression()) { error in
  463. guard let error = error as? E else {
  464. return XCTFail("Error had unexpected type '\(type(of: error))'")
  465. }
  466. errorHandler(error)
  467. }
  468. }
  469. }
  470. extension SnippetBasedTranslatorTests {
  471. private func makeCodeGenerationRequest(services: [ServiceDescriptor]) -> CodeGenerationRequest {
  472. return CodeGenerationRequest(
  473. fileName: "test.grpc",
  474. leadingTrivia: "Some really exciting license header 2023.",
  475. dependencies: [],
  476. services: services,
  477. lookupSerializer: {
  478. "ProtobufSerializer<\($0)>()"
  479. },
  480. lookupDeserializer: {
  481. "ProtobufDeserializer<\($0)>()"
  482. }
  483. )
  484. }
  485. }
  486. #endif // os(macOS) || os(Linux)